1use crate::error::LingerError;
2use crate::stream::{SseEvent, SseStream};
3use crate::transport::BodyStream;
4use crate::RequestId;
5use futures_core::Stream;
6use serde::ser::SerializeStruct;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::BTreeMap;
10use std::pin::Pin;
11use std::task::{Context, Poll};
12
13#[derive(Clone, Debug, Serialize, PartialEq)]
16#[non_exhaustive]
17pub struct CreateResponseRequest {
18 pub model: String,
21 pub input: ResponseInput,
24 #[serde(skip_serializing_if = "Option::is_none")]
27 pub instructions: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
31 pub personality: Option<String>,
32 #[serde(skip_serializing_if = "Option::is_none")]
35 pub previous_response_id: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
39 pub conversation: Option<ResponseConversation>,
40 #[serde(skip_serializing_if = "Option::is_none")]
43 pub context_management: Option<Vec<ResponseContextManagement>>,
44 #[serde(skip_serializing_if = "Option::is_none")]
47 pub moderation: Option<ResponseModeration>,
48 #[serde(skip_serializing_if = "Vec::is_empty")]
51 pub tools: Vec<Value>,
52 #[serde(skip_serializing_if = "Option::is_none")]
55 pub tool_choice: Option<ResponseToolChoice>,
56 #[serde(skip_serializing_if = "Option::is_none")]
59 pub prompt: Option<ResponsePrompt>,
60 #[serde(skip_serializing_if = "Option::is_none")]
63 pub max_output_tokens: Option<u32>,
64 #[serde(skip_serializing_if = "Option::is_none")]
67 pub store: Option<bool>,
68 #[serde(skip_serializing_if = "Option::is_none")]
71 pub background: Option<bool>,
72 #[serde(skip_serializing_if = "Option::is_none")]
75 pub max_tool_calls: Option<u32>,
76 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
79 pub metadata: BTreeMap<String, String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
83 pub parallel_tool_calls: Option<bool>,
84 #[serde(skip_serializing_if = "Option::is_none")]
87 pub temperature: Option<f64>,
88 #[serde(skip_serializing_if = "Option::is_none")]
91 pub top_p: Option<f64>,
92 #[serde(skip_serializing_if = "Option::is_none")]
95 pub top_logprobs: Option<u8>,
96 #[serde(skip_serializing_if = "Option::is_none")]
99 pub reasoning: Option<ResponseReasoning>,
100 #[serde(skip_serializing_if = "Option::is_none")]
103 pub truncation: Option<ResponseTruncation>,
104 #[serde(skip_serializing_if = "Option::is_none")]
107 pub prompt_cache_key: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
111 pub prompt_cache_retention: Option<ResponsePromptCacheRetention>,
112 #[serde(skip_serializing_if = "Option::is_none")]
115 pub safety_identifier: Option<String>,
116 #[serde(skip_serializing_if = "Option::is_none")]
119 pub service_tier: Option<ResponseServiceTier>,
120 #[serde(skip_serializing_if = "Option::is_none")]
123 pub include: Option<Vec<ResponseInclude>>,
124 #[serde(skip_serializing_if = "Option::is_none")]
127 pub text: Option<ResponseTextConfig>,
128 #[serde(skip_serializing_if = "Option::is_none")]
131 pub stream_options: Option<StreamOptions>,
132 #[serde(skip_serializing_if = "Option::is_none")]
135 pub stream: Option<bool>,
136 #[serde(flatten)]
139 pub extra: BTreeMap<String, Value>,
140}
141
142impl CreateResponseRequest {
143 pub fn builder() -> CreateResponseRequestBuilder {
146 CreateResponseRequestBuilder::default()
147 }
148
149 pub(crate) fn into_streaming(mut self) -> Self {
150 self.stream = Some(true);
151 self
152 }
153}
154
155#[derive(Clone, Debug, Default)]
158#[non_exhaustive]
159pub struct CreateResponseRequestBuilder {
160 model: Option<String>,
161 input: Option<ResponseInput>,
162 instructions: Option<String>,
163 personality: Option<String>,
164 previous_response_id: Option<String>,
165 conversation: Option<ResponseConversation>,
166 context_management: Option<Vec<ResponseContextManagement>>,
167 moderation: Option<ResponseModeration>,
168 tools: Vec<Value>,
169 tool_choice: Option<ResponseToolChoice>,
170 prompt: Option<ResponsePrompt>,
171 max_output_tokens: Option<u32>,
172 store: Option<bool>,
173 background: Option<bool>,
174 max_tool_calls: Option<u32>,
175 metadata: BTreeMap<String, String>,
176 parallel_tool_calls: Option<bool>,
177 temperature: Option<f64>,
178 top_p: Option<f64>,
179 top_logprobs: Option<u8>,
180 reasoning: Option<ResponseReasoning>,
181 truncation: Option<ResponseTruncation>,
182 prompt_cache_key: Option<String>,
183 prompt_cache_retention: Option<ResponsePromptCacheRetention>,
184 safety_identifier: Option<String>,
185 service_tier: Option<ResponseServiceTier>,
186 include: Option<Vec<ResponseInclude>>,
187 text: Option<ResponseTextConfig>,
188 stream_options: Option<StreamOptions>,
189 extra: BTreeMap<String, Value>,
190}
191
192impl CreateResponseRequestBuilder {
193 pub fn model(mut self, model: impl Into<String>) -> Self {
196 self.model = Some(model.into());
197 self
198 }
199
200 pub fn input(mut self, input: impl Into<ResponseInput>) -> Self {
203 self.input = Some(input.into());
204 self
205 }
206
207 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
210 self.instructions = Some(instructions.into());
211 self
212 }
213
214 pub fn personality(mut self, personality: impl Into<String>) -> Self {
217 self.personality = Some(personality.into());
218 self
219 }
220
221 pub fn previous_response_id(mut self, previous_response_id: impl Into<String>) -> Self {
224 self.previous_response_id = Some(previous_response_id.into());
225 self
226 }
227
228 pub fn conversation(mut self, conversation: impl Into<ResponseConversation>) -> Self {
231 self.conversation = Some(conversation.into());
232 self
233 }
234
235 pub fn context_management<I>(mut self, context_management: I) -> Self
238 where
239 I: IntoIterator<Item = ResponseContextManagement>,
240 {
241 self.context_management = Some(context_management.into_iter().collect());
242 self
243 }
244
245 pub fn moderation(mut self, moderation: ResponseModeration) -> Self {
248 self.moderation = Some(moderation);
249 self
250 }
251
252 pub fn tools<T>(mut self, tools: impl IntoIterator<Item = T>) -> Self
255 where
256 T: Into<ResponseTool>,
257 {
258 self.tools = tools
259 .into_iter()
260 .map(|tool| tool.into().into_value())
261 .collect();
262 self
263 }
264
265 pub fn tool(mut self, tool: impl Into<ResponseTool>) -> Self {
268 self.tools.push(tool.into().into_value());
269 self
270 }
271
272 pub fn tool_choice(mut self, tool_choice: ResponseToolChoice) -> Self {
275 self.tool_choice = Some(tool_choice);
276 self
277 }
278
279 pub fn prompt(mut self, prompt: ResponsePrompt) -> Self {
282 self.prompt = Some(prompt);
283 self
284 }
285
286 pub fn max_output_tokens(mut self, max_output_tokens: u32) -> Self {
289 self.max_output_tokens = Some(max_output_tokens);
290 self
291 }
292
293 pub fn store(mut self, store: bool) -> Self {
296 self.store = Some(store);
297 self
298 }
299
300 pub fn background(mut self, background: bool) -> Self {
303 self.background = Some(background);
304 self
305 }
306
307 pub fn max_tool_calls(mut self, max_tool_calls: u32) -> Self {
310 self.max_tool_calls = Some(max_tool_calls);
311 self
312 }
313
314 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
317 self.metadata.insert(key.into(), value.into());
318 self
319 }
320
321 pub fn parallel_tool_calls(mut self, parallel_tool_calls: bool) -> Self {
324 self.parallel_tool_calls = Some(parallel_tool_calls);
325 self
326 }
327
328 pub fn temperature(mut self, temperature: f64) -> Self {
331 self.temperature = Some(temperature);
332 self
333 }
334
335 pub fn top_p(mut self, top_p: f64) -> Self {
338 self.top_p = Some(top_p);
339 self
340 }
341
342 pub fn top_logprobs(mut self, top_logprobs: u8) -> Self {
345 self.top_logprobs = Some(top_logprobs);
346 self
347 }
348
349 pub fn reasoning(mut self, reasoning: ResponseReasoning) -> Self {
352 self.reasoning = Some(reasoning);
353 self
354 }
355
356 pub fn truncation(mut self, truncation: ResponseTruncation) -> Self {
359 self.truncation = Some(truncation);
360 self
361 }
362
363 pub fn prompt_cache_key(mut self, prompt_cache_key: impl Into<String>) -> Self {
366 self.prompt_cache_key = Some(prompt_cache_key.into());
367 self
368 }
369
370 pub fn prompt_cache_retention(
373 mut self,
374 prompt_cache_retention: ResponsePromptCacheRetention,
375 ) -> Self {
376 self.prompt_cache_retention = Some(prompt_cache_retention);
377 self
378 }
379
380 pub fn safety_identifier(mut self, safety_identifier: impl Into<String>) -> Self {
383 self.safety_identifier = Some(safety_identifier.into());
384 self
385 }
386
387 pub fn service_tier(mut self, service_tier: ResponseServiceTier) -> Self {
390 self.service_tier = Some(service_tier);
391 self
392 }
393
394 pub fn include<I>(mut self, include: I) -> Self
397 where
398 I: IntoIterator<Item = ResponseInclude>,
399 {
400 self.include = Some(include.into_iter().collect());
401 self
402 }
403
404 pub fn text(mut self, text: ResponseTextConfig) -> Self {
407 self.text = Some(text);
408 self
409 }
410
411 pub fn stream_options(mut self, stream_options: StreamOptions) -> Self {
414 self.stream_options = Some(stream_options);
415 self
416 }
417
418 pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
421 self.extra.insert(name.into(), value);
422 self
423 }
424
425 pub fn build(self) -> Result<CreateResponseRequest, LingerError> {
428 let model = self
429 .model
430 .filter(|value| !value.trim().is_empty())
431 .ok_or_else(|| LingerError::invalid_config("model is required"))?;
432 let input = self
433 .input
434 .ok_or_else(|| LingerError::invalid_config("input is required"))?;
435 if self
436 .max_output_tokens
437 .is_some_and(|max_output_tokens| max_output_tokens < 16)
438 {
439 return Err(LingerError::invalid_config(
440 "max_output_tokens must be at least 16",
441 ));
442 }
443 if self
444 .temperature
445 .is_some_and(|temperature| !(0.0..=2.0).contains(&temperature))
446 {
447 return Err(LingerError::invalid_config(
448 "temperature must be between 0.0 and 2.0",
449 ));
450 }
451 if self
452 .top_p
453 .is_some_and(|top_p| !(0.0..=1.0).contains(&top_p))
454 {
455 return Err(LingerError::invalid_config(
456 "top_p must be between 0.0 and 1.0",
457 ));
458 }
459 if self
460 .top_logprobs
461 .is_some_and(|top_logprobs| top_logprobs > 20)
462 {
463 return Err(LingerError::invalid_config(
464 "top_logprobs must be between 0 and 20",
465 ));
466 }
467 validate_optional_string("prompt_cache_key", self.prompt_cache_key.as_deref())?;
468 validate_optional_string("safety_identifier", self.safety_identifier.as_deref())?;
469 validate_optional_string("personality", self.personality.as_deref())?;
470 if let Some(conversation) = &self.conversation {
471 validate_optional_string("conversation", Some(conversation.id()))?;
472 }
473 if self.previous_response_id.is_some() && self.conversation.is_some() {
474 return Err(LingerError::invalid_config(
475 "previous_response_id cannot be used with conversation",
476 ));
477 }
478 if let Some(context_management) = &self.context_management {
479 validate_context_management(context_management)?;
480 }
481 if let Some(moderation) = &self.moderation {
482 validate_moderation(moderation)?;
483 }
484 validate_tools(&self.tools)?;
485 if let Some(tool_choice) = &self.tool_choice {
486 validate_tool_choice(tool_choice)?;
487 }
488 if let Some(prompt) = &self.prompt {
489 validate_prompt(prompt)?;
490 }
491 if let Some(text) = &self.text {
492 validate_text_config(text)?;
493 }
494 if self
495 .personality
496 .as_deref()
497 .is_some_and(|value| value.chars().count() > 64)
498 {
499 return Err(LingerError::invalid_config(
500 "personality must be at most 64 characters",
501 ));
502 }
503 validate_metadata(&self.metadata)?;
504 if self
505 .safety_identifier
506 .as_deref()
507 .is_some_and(|value| value.chars().count() > 64)
508 {
509 return Err(LingerError::invalid_config(
510 "safety_identifier must be at most 64 characters",
511 ));
512 }
513 validate_extra_fields(&self.extra)?;
514 Ok(CreateResponseRequest {
515 model,
516 input,
517 instructions: self.instructions,
518 personality: self.personality,
519 previous_response_id: self.previous_response_id,
520 conversation: self.conversation,
521 context_management: self.context_management,
522 moderation: self.moderation,
523 tools: self.tools,
524 tool_choice: self.tool_choice,
525 prompt: self.prompt,
526 max_output_tokens: self.max_output_tokens,
527 store: self.store,
528 background: self.background,
529 max_tool_calls: self.max_tool_calls,
530 metadata: self.metadata,
531 parallel_tool_calls: self.parallel_tool_calls,
532 temperature: self.temperature,
533 top_p: self.top_p,
534 top_logprobs: self.top_logprobs,
535 reasoning: self.reasoning,
536 truncation: self.truncation,
537 prompt_cache_key: self.prompt_cache_key,
538 prompt_cache_retention: self.prompt_cache_retention,
539 safety_identifier: self.safety_identifier,
540 service_tier: self.service_tier,
541 include: self.include,
542 text: self.text,
543 stream_options: self.stream_options,
544 stream: None,
545 extra: self.extra,
546 })
547 }
548}
549
550#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
553#[non_exhaustive]
554pub enum ResponseInclude {
555 #[serde(rename = "file_search_call.results")]
558 FileSearchCallResults,
559 #[serde(rename = "web_search_call.results")]
562 WebSearchCallResults,
563 #[serde(rename = "web_search_call.action.sources")]
566 WebSearchCallActionSources,
567 #[serde(rename = "message.input_image.image_url")]
570 MessageInputImageImageUrl,
571 #[serde(rename = "computer_call_output.output.image_url")]
574 ComputerCallOutputOutputImageUrl,
575 #[serde(rename = "code_interpreter_call.outputs")]
578 CodeInterpreterCallOutputs,
579 #[serde(rename = "reasoning.encrypted_content")]
582 ReasoningEncryptedContent,
583 #[serde(rename = "message.output_text.logprobs")]
586 MessageOutputTextLogprobs,
587}
588
589#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
592#[serde(rename_all = "snake_case")]
593#[non_exhaustive]
594pub enum ResponseTruncation {
595 Auto,
598 Disabled,
601}
602
603#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
606#[serde(untagged)]
607#[non_exhaustive]
608pub enum ResponseConversation {
609 Id(String),
612 Object {
615 id: String,
618 },
619}
620
621impl ResponseConversation {
622 pub fn object(id: impl Into<String>) -> Self {
625 Self::Object { id: id.into() }
626 }
627
628 fn id(&self) -> &str {
629 match self {
630 Self::Id(id) => id,
631 Self::Object { id } => id,
632 }
633 }
634}
635
636impl From<String> for ResponseConversation {
637 fn from(value: String) -> Self {
638 Self::Id(value)
639 }
640}
641
642impl From<&str> for ResponseConversation {
643 fn from(value: &str) -> Self {
644 Self::Id(value.to_string())
645 }
646}
647
648#[derive(Clone, Debug, Serialize, PartialEq)]
651#[non_exhaustive]
652pub struct ResponsePrompt {
653 pub id: String,
656 #[serde(skip_serializing_if = "Option::is_none")]
659 pub version: Option<String>,
660 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
663 pub variables: BTreeMap<String, Value>,
664}
665
666impl ResponsePrompt {
667 pub fn new(id: impl Into<String>) -> Self {
670 Self {
671 id: id.into(),
672 version: None,
673 variables: BTreeMap::new(),
674 }
675 }
676
677 pub fn version(mut self, version: impl Into<String>) -> Self {
680 self.version = Some(version.into());
681 self
682 }
683
684 pub fn variable(mut self, name: impl Into<String>, value: Value) -> Self {
687 self.variables.insert(name.into(), value);
688 self
689 }
690}
691
692#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
695#[non_exhaustive]
696pub struct ResponseContextManagement {
697 #[serde(rename = "type")]
700 pub kind: ResponseContextManagementType,
701 #[serde(skip_serializing_if = "Option::is_none")]
704 pub compact_threshold: Option<u32>,
705}
706
707impl ResponseContextManagement {
708 pub fn compaction() -> Self {
711 Self {
712 kind: ResponseContextManagementType::Compaction,
713 compact_threshold: None,
714 }
715 }
716
717 pub fn compact_threshold(mut self, compact_threshold: u32) -> Self {
720 self.compact_threshold = Some(compact_threshold);
721 self
722 }
723}
724
725#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
728#[serde(rename_all = "snake_case")]
729#[non_exhaustive]
730pub enum ResponseContextManagementType {
731 Compaction,
734}
735
736#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
739#[non_exhaustive]
740pub struct ResponseModeration {
741 pub model: String,
744}
745
746impl ResponseModeration {
747 pub fn new(model: impl Into<String>) -> Self {
750 Self {
751 model: model.into(),
752 }
753 }
754}
755
756#[derive(Clone, Debug, PartialEq)]
759#[non_exhaustive]
760pub struct ResponseTool {
761 value: Value,
762}
763
764impl ResponseTool {
765 pub fn raw(value: Value) -> Self {
768 Self { value }
769 }
770
771 pub fn into_value(self) -> Value {
774 self.value
775 }
776}
777
778impl From<Value> for ResponseTool {
779 fn from(value: Value) -> Self {
780 Self::raw(value)
781 }
782}
783
784impl From<ResponseFunctionTool> for ResponseTool {
785 fn from(tool: ResponseFunctionTool) -> Self {
786 Self {
787 value: serde_json::to_value(tool)
788 .expect("serializing a response function tool should not fail"),
789 }
790 }
791}
792
793impl From<ResponseFileSearchTool> for ResponseTool {
794 fn from(tool: ResponseFileSearchTool) -> Self {
795 Self {
796 value: serde_json::to_value(tool)
797 .expect("serializing a response file search tool should not fail"),
798 }
799 }
800}
801
802impl From<ResponseComputerTool> for ResponseTool {
803 fn from(tool: ResponseComputerTool) -> Self {
804 Self {
805 value: serde_json::to_value(tool)
806 .expect("serializing a response computer tool should not fail"),
807 }
808 }
809}
810
811impl From<ResponseLocalShellTool> for ResponseTool {
812 fn from(tool: ResponseLocalShellTool) -> Self {
813 Self {
814 value: serde_json::to_value(tool)
815 .expect("serializing a response local shell tool should not fail"),
816 }
817 }
818}
819
820impl From<ResponseApplyPatchTool> for ResponseTool {
821 fn from(tool: ResponseApplyPatchTool) -> Self {
822 Self {
823 value: serde_json::to_value(tool)
824 .expect("serializing a response apply patch tool should not fail"),
825 }
826 }
827}
828
829impl From<ResponseCustomTool> for ResponseTool {
830 fn from(tool: ResponseCustomTool) -> Self {
831 Self {
832 value: serde_json::to_value(tool)
833 .expect("serializing a response custom tool should not fail"),
834 }
835 }
836}
837
838impl From<ResponseNamespaceTool> for ResponseTool {
839 fn from(tool: ResponseNamespaceTool) -> Self {
840 Self {
841 value: serde_json::to_value(tool)
842 .expect("serializing a response namespace tool should not fail"),
843 }
844 }
845}
846
847impl From<ResponseToolSearchTool> for ResponseTool {
848 fn from(tool: ResponseToolSearchTool) -> Self {
849 Self {
850 value: serde_json::to_value(tool)
851 .expect("serializing a response tool search tool should not fail"),
852 }
853 }
854}
855
856impl From<ResponseShellTool> for ResponseTool {
857 fn from(tool: ResponseShellTool) -> Self {
858 Self {
859 value: serde_json::to_value(tool)
860 .expect("serializing a response shell tool should not fail"),
861 }
862 }
863}
864
865impl From<ResponseCodeInterpreterTool> for ResponseTool {
866 fn from(tool: ResponseCodeInterpreterTool) -> Self {
867 Self {
868 value: serde_json::to_value(tool)
869 .expect("serializing a response code interpreter tool should not fail"),
870 }
871 }
872}
873
874impl From<ResponseImageGenerationTool> for ResponseTool {
875 fn from(tool: ResponseImageGenerationTool) -> Self {
876 Self {
877 value: serde_json::to_value(tool)
878 .expect("serializing a response image generation tool should not fail"),
879 }
880 }
881}
882
883impl From<ResponseMcpTool> for ResponseTool {
884 fn from(tool: ResponseMcpTool) -> Self {
885 Self {
886 value: serde_json::to_value(tool)
887 .expect("serializing a response MCP tool should not fail"),
888 }
889 }
890}
891
892impl From<ResponseWebSearchTool> for ResponseTool {
893 fn from(tool: ResponseWebSearchTool) -> Self {
894 Self {
895 value: serde_json::to_value(tool)
896 .expect("serializing a response web search tool should not fail"),
897 }
898 }
899}
900
901#[derive(Clone, Debug, Serialize, PartialEq)]
904#[non_exhaustive]
905pub struct ResponseComputerTool {
906 #[serde(rename = "type")]
907 kind: &'static str,
908 #[serde(skip_serializing_if = "Option::is_none")]
911 pub environment: Option<ResponseComputerEnvironment>,
912 #[serde(skip_serializing_if = "Option::is_none")]
915 pub display_width: Option<u32>,
916 #[serde(skip_serializing_if = "Option::is_none")]
919 pub display_height: Option<u32>,
920}
921
922impl ResponseComputerTool {
923 pub fn computer() -> Self {
926 Self {
927 kind: "computer",
928 environment: None,
929 display_width: None,
930 display_height: None,
931 }
932 }
933
934 pub fn computer_use_preview(
937 environment: ResponseComputerEnvironment,
938 display_width: u32,
939 display_height: u32,
940 ) -> Self {
941 Self {
942 kind: "computer_use_preview",
943 environment: Some(environment),
944 display_width: Some(display_width),
945 display_height: Some(display_height),
946 }
947 }
948}
949
950#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
953#[non_exhaustive]
954pub enum ResponseComputerEnvironment {
955 #[serde(rename = "windows")]
958 Windows,
959 #[serde(rename = "mac")]
962 Mac,
963 #[serde(rename = "linux")]
966 Linux,
967 #[serde(rename = "ubuntu")]
970 Ubuntu,
971 #[serde(rename = "browser")]
974 Browser,
975}
976
977#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
980#[non_exhaustive]
981pub struct ResponseLocalShellTool {
982 #[serde(rename = "type")]
983 kind: &'static str,
984}
985
986impl ResponseLocalShellTool {
987 pub fn new() -> Self {
990 Self {
991 kind: "local_shell",
992 }
993 }
994}
995
996impl Default for ResponseLocalShellTool {
997 fn default() -> Self {
998 Self::new()
999 }
1000}
1001
1002#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
1005#[non_exhaustive]
1006pub struct ResponseApplyPatchTool {
1007 #[serde(rename = "type")]
1008 kind: &'static str,
1009}
1010
1011impl ResponseApplyPatchTool {
1012 pub fn new() -> Self {
1015 Self {
1016 kind: "apply_patch",
1017 }
1018 }
1019}
1020
1021impl Default for ResponseApplyPatchTool {
1022 fn default() -> Self {
1023 Self::new()
1024 }
1025}
1026
1027#[derive(Clone, Debug, Serialize, PartialEq)]
1030#[non_exhaustive]
1031pub struct ResponseCustomTool {
1032 #[serde(rename = "type")]
1033 kind: &'static str,
1034 pub name: String,
1037 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub description: Option<String>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1044 pub format: Option<ResponseCustomToolFormat>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1048 pub defer_loading: Option<bool>,
1049}
1050
1051impl ResponseCustomTool {
1052 pub fn new(name: impl Into<String>) -> Self {
1055 Self {
1056 kind: "custom",
1057 name: name.into(),
1058 description: None,
1059 format: None,
1060 defer_loading: None,
1061 }
1062 }
1063
1064 pub fn description(mut self, description: impl Into<String>) -> Self {
1067 self.description = Some(description.into());
1068 self
1069 }
1070
1071 pub fn format(mut self, format: ResponseCustomToolFormat) -> Self {
1074 self.format = Some(format);
1075 self
1076 }
1077
1078 pub fn defer_loading(mut self, defer_loading: bool) -> Self {
1081 self.defer_loading = Some(defer_loading);
1082 self
1083 }
1084}
1085
1086#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1089#[serde(tag = "type")]
1090#[non_exhaustive]
1091pub enum ResponseCustomToolFormat {
1092 #[serde(rename = "text")]
1095 Text,
1096 #[serde(rename = "grammar")]
1099 Grammar {
1100 syntax: ResponseCustomToolGrammarSyntax,
1103 definition: String,
1106 },
1107}
1108
1109impl ResponseCustomToolFormat {
1110 pub fn text() -> Self {
1113 Self::Text
1114 }
1115
1116 pub fn grammar(syntax: ResponseCustomToolGrammarSyntax, definition: impl Into<String>) -> Self {
1119 Self::Grammar {
1120 syntax,
1121 definition: definition.into(),
1122 }
1123 }
1124}
1125
1126#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1129#[non_exhaustive]
1130pub enum ResponseCustomToolGrammarSyntax {
1131 #[serde(rename = "lark")]
1134 Lark,
1135 #[serde(rename = "regex")]
1138 Regex,
1139}
1140
1141#[derive(Clone, Debug, Serialize, PartialEq)]
1144#[non_exhaustive]
1145pub struct ResponseNamespaceTool {
1146 #[serde(rename = "type")]
1147 kind: &'static str,
1148 pub name: String,
1151 pub description: String,
1154 pub tools: Vec<Value>,
1157}
1158
1159impl ResponseNamespaceTool {
1160 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
1163 Self {
1164 kind: "namespace",
1165 name: name.into(),
1166 description: description.into(),
1167 tools: Vec::new(),
1168 }
1169 }
1170
1171 pub fn tool<T>(mut self, tool: T) -> Self
1174 where
1175 T: Into<ResponseTool>,
1176 {
1177 self.tools.push(tool.into().into_value());
1178 self
1179 }
1180
1181 pub fn tools<I, T>(mut self, tools: I) -> Self
1184 where
1185 I: IntoIterator<Item = T>,
1186 T: Into<ResponseTool>,
1187 {
1188 self.tools = tools
1189 .into_iter()
1190 .map(|tool| tool.into().into_value())
1191 .collect();
1192 self
1193 }
1194}
1195
1196#[derive(Clone, Debug, Serialize, PartialEq)]
1199#[non_exhaustive]
1200pub struct ResponseToolSearchTool {
1201 #[serde(rename = "type")]
1202 kind: &'static str,
1203 #[serde(skip_serializing_if = "Option::is_none")]
1206 pub execution: Option<ResponseToolSearchExecution>,
1207 #[serde(skip_serializing_if = "Option::is_none")]
1210 pub description: Option<String>,
1211 #[serde(skip_serializing_if = "Option::is_none")]
1214 pub parameters: Option<Value>,
1215}
1216
1217impl ResponseToolSearchTool {
1218 pub fn new() -> Self {
1221 Self {
1222 kind: "tool_search",
1223 execution: None,
1224 description: None,
1225 parameters: None,
1226 }
1227 }
1228
1229 pub fn server() -> Self {
1232 Self::new().execution(ResponseToolSearchExecution::Server)
1233 }
1234
1235 pub fn client() -> Self {
1238 Self::new().execution(ResponseToolSearchExecution::Client)
1239 }
1240
1241 pub fn execution(mut self, execution: ResponseToolSearchExecution) -> Self {
1244 self.execution = Some(execution);
1245 self
1246 }
1247
1248 pub fn description(mut self, description: impl Into<String>) -> Self {
1251 self.description = Some(description.into());
1252 self
1253 }
1254
1255 pub fn parameters(mut self, parameters: Value) -> Self {
1258 self.parameters = Some(parameters);
1259 self
1260 }
1261}
1262
1263impl Default for ResponseToolSearchTool {
1264 fn default() -> Self {
1265 Self::new()
1266 }
1267}
1268
1269#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1272#[non_exhaustive]
1273pub enum ResponseToolSearchExecution {
1274 #[serde(rename = "server")]
1277 Server,
1278 #[serde(rename = "client")]
1281 Client,
1282}
1283
1284#[derive(Clone, Debug, Serialize, PartialEq)]
1287#[non_exhaustive]
1288pub struct ResponseShellTool {
1289 #[serde(rename = "type")]
1290 kind: &'static str,
1291 #[serde(skip_serializing_if = "Option::is_none")]
1294 pub environment: Option<Value>,
1295}
1296
1297impl ResponseShellTool {
1298 pub fn new() -> Self {
1301 Self {
1302 kind: "shell",
1303 environment: None,
1304 }
1305 }
1306
1307 pub fn local() -> Self {
1310 Self::new().environment(shell_environment_type("local"))
1311 }
1312
1313 pub fn container_auto() -> Self {
1316 Self::new().environment(shell_environment_type("container_auto"))
1317 }
1318
1319 pub fn container_reference(container_id: impl Into<String>) -> Self {
1322 let mut environment = serde_json::Map::new();
1323 environment.insert(
1324 "type".to_string(),
1325 Value::String("container_reference".to_string()),
1326 );
1327 environment.insert(
1328 "container_id".to_string(),
1329 Value::String(container_id.into()),
1330 );
1331 Self::new().environment(Value::Object(environment))
1332 }
1333
1334 pub fn environment(mut self, environment: Value) -> Self {
1337 self.environment = Some(environment);
1338 self
1339 }
1340}
1341
1342impl Default for ResponseShellTool {
1343 fn default() -> Self {
1344 Self::new()
1345 }
1346}
1347
1348fn shell_environment_type(kind: &'static str) -> Value {
1349 let mut environment = serde_json::Map::new();
1350 environment.insert("type".to_string(), Value::String(kind.to_string()));
1351 Value::Object(environment)
1352}
1353
1354#[derive(Clone, Debug, Serialize, PartialEq)]
1357#[non_exhaustive]
1358pub struct ResponseFunctionTool {
1359 #[serde(rename = "type")]
1360 kind: &'static str,
1361 pub name: String,
1364 #[serde(skip_serializing_if = "Option::is_none")]
1367 pub description: Option<String>,
1368 pub parameters: Value,
1371 pub strict: bool,
1374 #[serde(skip_serializing_if = "Option::is_none")]
1377 pub defer_loading: Option<bool>,
1378}
1379
1380impl ResponseFunctionTool {
1381 pub fn new(name: impl Into<String>, parameters: Value) -> Self {
1384 Self {
1385 kind: "function",
1386 name: name.into(),
1387 description: None,
1388 parameters,
1389 strict: true,
1390 defer_loading: None,
1391 }
1392 }
1393
1394 pub fn description(mut self, description: impl Into<String>) -> Self {
1397 self.description = Some(description.into());
1398 self
1399 }
1400
1401 pub fn strict(mut self, strict: bool) -> Self {
1404 self.strict = strict;
1405 self
1406 }
1407
1408 pub fn defer_loading(mut self, defer_loading: bool) -> Self {
1411 self.defer_loading = Some(defer_loading);
1412 self
1413 }
1414}
1415
1416#[derive(Clone, Debug, Serialize, PartialEq)]
1419#[non_exhaustive]
1420pub struct ResponseFileSearchTool {
1421 #[serde(rename = "type")]
1422 kind: &'static str,
1423 pub vector_store_ids: Vec<String>,
1426 #[serde(skip_serializing_if = "Option::is_none")]
1429 pub max_num_results: Option<u8>,
1430 #[serde(skip_serializing_if = "Option::is_none")]
1433 pub ranking_options: Option<Value>,
1434 #[serde(skip_serializing_if = "Option::is_none")]
1437 pub filters: Option<Value>,
1438}
1439
1440impl ResponseFileSearchTool {
1441 pub fn new<I, S>(vector_store_ids: I) -> Self
1444 where
1445 I: IntoIterator<Item = S>,
1446 S: Into<String>,
1447 {
1448 Self {
1449 kind: "file_search",
1450 vector_store_ids: vector_store_ids.into_iter().map(Into::into).collect(),
1451 max_num_results: None,
1452 ranking_options: None,
1453 filters: None,
1454 }
1455 }
1456
1457 pub fn max_num_results(mut self, max_num_results: u8) -> Self {
1460 self.max_num_results = Some(max_num_results);
1461 self
1462 }
1463
1464 pub fn ranking_options(mut self, ranking_options: Value) -> Self {
1467 self.ranking_options = Some(ranking_options);
1468 self
1469 }
1470
1471 pub fn filters(mut self, filters: Value) -> Self {
1474 self.filters = Some(filters);
1475 self
1476 }
1477}
1478
1479#[derive(Clone, Debug, Serialize, PartialEq)]
1482#[non_exhaustive]
1483pub struct ResponseCodeInterpreterTool {
1484 #[serde(rename = "type")]
1485 kind: &'static str,
1486 pub container: ResponseCodeInterpreterContainer,
1489}
1490
1491impl ResponseCodeInterpreterTool {
1492 pub fn container_id(container_id: impl Into<String>) -> Self {
1495 Self {
1496 kind: "code_interpreter",
1497 container: ResponseCodeInterpreterContainer::Id(container_id.into()),
1498 }
1499 }
1500
1501 pub fn auto() -> Self {
1504 Self::auto_container(ResponseCodeInterpreterAutoContainer::new())
1505 }
1506
1507 pub fn auto_container(container: ResponseCodeInterpreterAutoContainer) -> Self {
1510 Self {
1511 kind: "code_interpreter",
1512 container: ResponseCodeInterpreterContainer::Auto(container),
1513 }
1514 }
1515
1516 pub fn file_ids<I, S>(mut self, file_ids: I) -> Self
1519 where
1520 I: IntoIterator<Item = S>,
1521 S: Into<String>,
1522 {
1523 if let ResponseCodeInterpreterContainer::Auto(container) = &mut self.container {
1524 container.file_ids = file_ids.into_iter().map(Into::into).collect();
1525 }
1526 self
1527 }
1528
1529 pub fn memory_limit(mut self, memory_limit: ResponseCodeInterpreterMemoryLimit) -> Self {
1532 if let ResponseCodeInterpreterContainer::Auto(container) = &mut self.container {
1533 container.memory_limit = Some(memory_limit);
1534 }
1535 self
1536 }
1537
1538 pub fn network_policy(mut self, network_policy: Value) -> Self {
1541 if let ResponseCodeInterpreterContainer::Auto(container) = &mut self.container {
1542 container.network_policy = Some(network_policy);
1543 }
1544 self
1545 }
1546}
1547
1548#[derive(Clone, Debug, Serialize, PartialEq)]
1551#[serde(untagged)]
1552#[non_exhaustive]
1553pub enum ResponseCodeInterpreterContainer {
1554 Id(String),
1557 Auto(ResponseCodeInterpreterAutoContainer),
1560}
1561
1562#[derive(Clone, Debug, Serialize, PartialEq)]
1565#[non_exhaustive]
1566pub struct ResponseCodeInterpreterAutoContainer {
1567 #[serde(rename = "type")]
1568 kind: &'static str,
1569 #[serde(skip_serializing_if = "Vec::is_empty")]
1572 pub file_ids: Vec<String>,
1573 #[serde(skip_serializing_if = "Option::is_none")]
1576 pub memory_limit: Option<ResponseCodeInterpreterMemoryLimit>,
1577 #[serde(skip_serializing_if = "Option::is_none")]
1580 pub network_policy: Option<Value>,
1581}
1582
1583impl ResponseCodeInterpreterAutoContainer {
1584 pub fn new() -> Self {
1587 Self {
1588 kind: "auto",
1589 file_ids: Vec::new(),
1590 memory_limit: None,
1591 network_policy: None,
1592 }
1593 }
1594
1595 pub fn file_ids<I, S>(mut self, file_ids: I) -> Self
1598 where
1599 I: IntoIterator<Item = S>,
1600 S: Into<String>,
1601 {
1602 self.file_ids = file_ids.into_iter().map(Into::into).collect();
1603 self
1604 }
1605
1606 pub fn memory_limit(mut self, memory_limit: ResponseCodeInterpreterMemoryLimit) -> Self {
1609 self.memory_limit = Some(memory_limit);
1610 self
1611 }
1612
1613 pub fn network_policy(mut self, network_policy: Value) -> Self {
1616 self.network_policy = Some(network_policy);
1617 self
1618 }
1619}
1620
1621impl Default for ResponseCodeInterpreterAutoContainer {
1622 fn default() -> Self {
1623 Self::new()
1624 }
1625}
1626
1627#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1630#[non_exhaustive]
1631pub enum ResponseCodeInterpreterMemoryLimit {
1632 #[serde(rename = "1g")]
1635 OneGigabyte,
1636 #[serde(rename = "4g")]
1639 FourGigabytes,
1640 #[serde(rename = "16g")]
1643 SixteenGigabytes,
1644 #[serde(rename = "64g")]
1647 SixtyFourGigabytes,
1648}
1649
1650#[derive(Clone, Debug, Serialize, PartialEq)]
1653#[non_exhaustive]
1654pub struct ResponseImageGenerationTool {
1655 #[serde(rename = "type")]
1656 kind: &'static str,
1657 #[serde(skip_serializing_if = "Option::is_none")]
1660 pub model: Option<String>,
1661 #[serde(skip_serializing_if = "Option::is_none")]
1664 pub quality: Option<ResponseImageGenerationQuality>,
1665 #[serde(skip_serializing_if = "Option::is_none")]
1668 pub size: Option<String>,
1669 #[serde(skip_serializing_if = "Option::is_none")]
1672 pub output_format: Option<ResponseImageGenerationOutputFormat>,
1673 #[serde(skip_serializing_if = "Option::is_none")]
1676 pub output_compression: Option<u8>,
1677 #[serde(skip_serializing_if = "Option::is_none")]
1680 pub moderation: Option<ResponseImageGenerationModeration>,
1681 #[serde(skip_serializing_if = "Option::is_none")]
1684 pub background: Option<ResponseImageGenerationBackground>,
1685 #[serde(skip_serializing_if = "Option::is_none")]
1688 pub input_fidelity: Option<ResponseImageGenerationInputFidelity>,
1689 #[serde(skip_serializing_if = "Option::is_none")]
1692 pub input_image_mask: Option<ResponseImageGenerationMask>,
1693 #[serde(skip_serializing_if = "Option::is_none")]
1696 pub partial_images: Option<u8>,
1697 #[serde(skip_serializing_if = "Option::is_none")]
1700 pub action: Option<ResponseImageGenerationAction>,
1701}
1702
1703impl ResponseImageGenerationTool {
1704 pub fn new() -> Self {
1707 Self {
1708 kind: "image_generation",
1709 model: None,
1710 quality: None,
1711 size: None,
1712 output_format: None,
1713 output_compression: None,
1714 moderation: None,
1715 background: None,
1716 input_fidelity: None,
1717 input_image_mask: None,
1718 partial_images: None,
1719 action: None,
1720 }
1721 }
1722
1723 pub fn model(mut self, model: impl Into<String>) -> Self {
1726 self.model = Some(model.into());
1727 self
1728 }
1729
1730 pub fn quality(mut self, quality: ResponseImageGenerationQuality) -> Self {
1733 self.quality = Some(quality);
1734 self
1735 }
1736
1737 pub fn size(mut self, size: impl Into<String>) -> Self {
1740 self.size = Some(size.into());
1741 self
1742 }
1743
1744 pub fn output_format(mut self, output_format: ResponseImageGenerationOutputFormat) -> Self {
1747 self.output_format = Some(output_format);
1748 self
1749 }
1750
1751 pub fn output_compression(mut self, output_compression: u8) -> Self {
1754 self.output_compression = Some(output_compression);
1755 self
1756 }
1757
1758 pub fn moderation(mut self, moderation: ResponseImageGenerationModeration) -> Self {
1761 self.moderation = Some(moderation);
1762 self
1763 }
1764
1765 pub fn background(mut self, background: ResponseImageGenerationBackground) -> Self {
1768 self.background = Some(background);
1769 self
1770 }
1771
1772 pub fn input_fidelity(mut self, input_fidelity: ResponseImageGenerationInputFidelity) -> Self {
1775 self.input_fidelity = Some(input_fidelity);
1776 self
1777 }
1778
1779 pub fn input_image_mask(mut self, input_image_mask: ResponseImageGenerationMask) -> Self {
1782 self.input_image_mask = Some(input_image_mask);
1783 self
1784 }
1785
1786 pub fn partial_images(mut self, partial_images: u8) -> Self {
1789 self.partial_images = Some(partial_images);
1790 self
1791 }
1792
1793 pub fn action(mut self, action: ResponseImageGenerationAction) -> Self {
1796 self.action = Some(action);
1797 self
1798 }
1799}
1800
1801impl Default for ResponseImageGenerationTool {
1802 fn default() -> Self {
1803 Self::new()
1804 }
1805}
1806
1807#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
1810#[non_exhaustive]
1811pub struct ResponseImageGenerationMask {
1812 #[serde(skip_serializing_if = "Option::is_none")]
1815 pub image_url: Option<String>,
1816 #[serde(skip_serializing_if = "Option::is_none")]
1819 pub file_id: Option<String>,
1820}
1821
1822impl ResponseImageGenerationMask {
1823 pub fn image_url(image_url: impl Into<String>) -> Self {
1826 Self {
1827 image_url: Some(image_url.into()),
1828 file_id: None,
1829 }
1830 }
1831
1832 pub fn file_id(file_id: impl Into<String>) -> Self {
1835 Self {
1836 image_url: None,
1837 file_id: Some(file_id.into()),
1838 }
1839 }
1840}
1841
1842#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1845#[serde(rename_all = "snake_case")]
1846#[non_exhaustive]
1847pub enum ResponseImageGenerationQuality {
1848 Low,
1851 Medium,
1854 High,
1857 Auto,
1860}
1861
1862#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1865#[serde(rename_all = "snake_case")]
1866#[non_exhaustive]
1867pub enum ResponseImageGenerationOutputFormat {
1868 Png,
1871 Webp,
1874 Jpeg,
1877}
1878
1879#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1882#[serde(rename_all = "snake_case")]
1883#[non_exhaustive]
1884pub enum ResponseImageGenerationModeration {
1885 Auto,
1888 Low,
1891}
1892
1893#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1896#[serde(rename_all = "snake_case")]
1897#[non_exhaustive]
1898pub enum ResponseImageGenerationBackground {
1899 Transparent,
1902 Opaque,
1905 Auto,
1908}
1909
1910#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1913#[serde(rename_all = "snake_case")]
1914#[non_exhaustive]
1915pub enum ResponseImageGenerationInputFidelity {
1916 High,
1919 Low,
1922}
1923
1924#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
1927#[serde(rename_all = "snake_case")]
1928#[non_exhaustive]
1929pub enum ResponseImageGenerationAction {
1930 Generate,
1933 Edit,
1936 Auto,
1939}
1940
1941#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
1944#[non_exhaustive]
1945pub struct ResponseMcpTool {
1946 #[serde(rename = "type")]
1947 kind: &'static str,
1948 pub server_label: String,
1951 #[serde(skip_serializing_if = "Option::is_none")]
1954 pub server_url: Option<String>,
1955 #[serde(skip_serializing_if = "Option::is_none")]
1958 pub connector_id: Option<ResponseMcpConnector>,
1959 #[serde(skip_serializing_if = "Option::is_none")]
1962 pub authorization: Option<String>,
1963 #[serde(skip_serializing_if = "Option::is_none")]
1966 pub server_description: Option<String>,
1967 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
1970 pub headers: BTreeMap<String, String>,
1971 #[serde(skip_serializing_if = "Option::is_none")]
1974 pub allowed_tools: Option<ResponseMcpAllowedTools>,
1975 #[serde(skip_serializing_if = "Option::is_none")]
1978 pub require_approval: Option<ResponseMcpRequireApproval>,
1979 #[serde(skip_serializing_if = "Option::is_none")]
1982 pub defer_loading: Option<bool>,
1983}
1984
1985impl ResponseMcpTool {
1986 pub fn server_url(server_label: impl Into<String>, server_url: impl Into<String>) -> Self {
1989 Self {
1990 kind: "mcp",
1991 server_label: server_label.into(),
1992 server_url: Some(server_url.into()),
1993 connector_id: None,
1994 authorization: None,
1995 server_description: None,
1996 headers: BTreeMap::new(),
1997 allowed_tools: None,
1998 require_approval: None,
1999 defer_loading: None,
2000 }
2001 }
2002
2003 pub fn connector(server_label: impl Into<String>, connector: ResponseMcpConnector) -> Self {
2006 Self {
2007 kind: "mcp",
2008 server_label: server_label.into(),
2009 server_url: None,
2010 connector_id: Some(connector),
2011 authorization: None,
2012 server_description: None,
2013 headers: BTreeMap::new(),
2014 allowed_tools: None,
2015 require_approval: None,
2016 defer_loading: None,
2017 }
2018 }
2019
2020 pub fn authorization(mut self, authorization: impl Into<String>) -> Self {
2023 self.authorization = Some(authorization.into());
2024 self
2025 }
2026
2027 pub fn server_description(mut self, server_description: impl Into<String>) -> Self {
2030 self.server_description = Some(server_description.into());
2031 self
2032 }
2033
2034 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
2037 self.headers.insert(name.into(), value.into());
2038 self
2039 }
2040
2041 pub fn allowed_tools<I, S>(mut self, tool_names: I) -> Self
2044 where
2045 I: IntoIterator<Item = S>,
2046 S: Into<String>,
2047 {
2048 self.allowed_tools = Some(ResponseMcpAllowedTools::Names(
2049 tool_names.into_iter().map(Into::into).collect(),
2050 ));
2051 self
2052 }
2053
2054 pub fn allowed_tool_filter(mut self, filter: ResponseMcpToolFilter) -> Self {
2057 self.allowed_tools = Some(ResponseMcpAllowedTools::Filter(filter));
2058 self
2059 }
2060
2061 pub fn require_approval(mut self, require_approval: ResponseMcpRequireApproval) -> Self {
2064 self.require_approval = Some(require_approval);
2065 self
2066 }
2067
2068 pub fn defer_loading(mut self, defer_loading: bool) -> Self {
2071 self.defer_loading = Some(defer_loading);
2072 self
2073 }
2074}
2075
2076#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2079#[non_exhaustive]
2080pub enum ResponseMcpConnector {
2081 #[serde(rename = "connector_dropbox")]
2084 Dropbox,
2085 #[serde(rename = "connector_gmail")]
2088 Gmail,
2089 #[serde(rename = "connector_googlecalendar")]
2092 GoogleCalendar,
2093 #[serde(rename = "connector_googledrive")]
2096 GoogleDrive,
2097 #[serde(rename = "connector_microsoftteams")]
2100 MicrosoftTeams,
2101 #[serde(rename = "connector_outlookcalendar")]
2104 OutlookCalendar,
2105 #[serde(rename = "connector_outlookemail")]
2108 OutlookEmail,
2109 #[serde(rename = "connector_sharepoint")]
2112 SharePoint,
2113}
2114
2115#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
2118#[serde(untagged)]
2119#[non_exhaustive]
2120pub enum ResponseMcpAllowedTools {
2121 Names(Vec<String>),
2124 Filter(ResponseMcpToolFilter),
2127}
2128
2129#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
2132#[non_exhaustive]
2133pub struct ResponseMcpToolFilter {
2134 #[serde(skip_serializing_if = "Vec::is_empty")]
2137 pub tool_names: Vec<String>,
2138 #[serde(skip_serializing_if = "Option::is_none")]
2141 pub read_only: Option<bool>,
2142}
2143
2144impl ResponseMcpToolFilter {
2145 pub fn new() -> Self {
2148 Self::default()
2149 }
2150
2151 pub fn tool_names<I, S>(mut self, tool_names: I) -> Self
2154 where
2155 I: IntoIterator<Item = S>,
2156 S: Into<String>,
2157 {
2158 self.tool_names = tool_names.into_iter().map(Into::into).collect();
2159 self
2160 }
2161
2162 pub fn read_only(mut self, read_only: bool) -> Self {
2165 self.read_only = Some(read_only);
2166 self
2167 }
2168}
2169
2170#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
2173#[serde(untagged)]
2174#[non_exhaustive]
2175pub enum ResponseMcpRequireApproval {
2176 Mode(ResponseMcpApprovalMode),
2179 Filter(ResponseMcpApprovalFilter),
2182}
2183
2184#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2187#[serde(rename_all = "snake_case")]
2188#[non_exhaustive]
2189pub enum ResponseMcpApprovalMode {
2190 Always,
2193 Never,
2196}
2197
2198#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
2201#[non_exhaustive]
2202pub struct ResponseMcpApprovalFilter {
2203 #[serde(skip_serializing_if = "Option::is_none")]
2206 pub always: Option<ResponseMcpToolFilter>,
2207 #[serde(skip_serializing_if = "Option::is_none")]
2210 pub never: Option<ResponseMcpToolFilter>,
2211}
2212
2213impl ResponseMcpApprovalFilter {
2214 pub fn new() -> Self {
2217 Self::default()
2218 }
2219
2220 pub fn always(mut self, filter: ResponseMcpToolFilter) -> Self {
2223 self.always = Some(filter);
2224 self
2225 }
2226
2227 pub fn never(mut self, filter: ResponseMcpToolFilter) -> Self {
2230 self.never = Some(filter);
2231 self
2232 }
2233}
2234
2235#[derive(Clone, Debug, Serialize, PartialEq)]
2238#[non_exhaustive]
2239pub struct ResponseWebSearchTool {
2240 #[serde(rename = "type")]
2241 kind: ResponseWebSearchToolType,
2242 #[serde(skip_serializing_if = "Option::is_none")]
2245 pub filters: Option<Value>,
2246 #[serde(skip_serializing_if = "Option::is_none")]
2249 pub user_location: Option<Value>,
2250 #[serde(skip_serializing_if = "Option::is_none")]
2253 pub search_context_size: Option<ResponseWebSearchContextSize>,
2254 #[serde(skip_serializing_if = "Vec::is_empty")]
2257 pub search_content_types: Vec<String>,
2258}
2259
2260impl ResponseWebSearchTool {
2261 pub fn web_search() -> Self {
2264 Self::new(ResponseWebSearchToolType::WebSearch)
2265 }
2266
2267 pub fn web_search_2025_08_26() -> Self {
2270 Self::new(ResponseWebSearchToolType::WebSearch20250826)
2271 }
2272
2273 pub fn web_search_preview() -> Self {
2276 Self::new(ResponseWebSearchToolType::WebSearchPreview)
2277 }
2278
2279 pub fn web_search_preview_2025_03_11() -> Self {
2282 Self::new(ResponseWebSearchToolType::WebSearchPreview20250311)
2283 }
2284
2285 fn new(kind: ResponseWebSearchToolType) -> Self {
2286 Self {
2287 kind,
2288 filters: None,
2289 user_location: None,
2290 search_context_size: None,
2291 search_content_types: Vec::new(),
2292 }
2293 }
2294
2295 pub fn filters(mut self, filters: Value) -> Self {
2298 self.filters = Some(filters);
2299 self
2300 }
2301
2302 pub fn user_location(mut self, user_location: Value) -> Self {
2305 self.user_location = Some(user_location);
2306 self
2307 }
2308
2309 pub fn search_context_size(
2312 mut self,
2313 search_context_size: ResponseWebSearchContextSize,
2314 ) -> Self {
2315 self.search_context_size = Some(search_context_size);
2316 self
2317 }
2318
2319 pub fn search_content_types<I, S>(mut self, search_content_types: I) -> Self
2322 where
2323 I: IntoIterator<Item = S>,
2324 S: Into<String>,
2325 {
2326 self.search_content_types = search_content_types.into_iter().map(Into::into).collect();
2327 self
2328 }
2329}
2330
2331#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
2334#[non_exhaustive]
2335pub enum ResponseWebSearchToolType {
2336 #[serde(rename = "web_search")]
2339 WebSearch,
2340 #[serde(rename = "web_search_2025_08_26")]
2343 WebSearch20250826,
2344 #[serde(rename = "web_search_preview")]
2347 WebSearchPreview,
2348 #[serde(rename = "web_search_preview_2025_03_11")]
2351 WebSearchPreview20250311,
2352}
2353
2354#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2357#[serde(rename_all = "snake_case")]
2358#[non_exhaustive]
2359pub enum ResponseWebSearchContextSize {
2360 Low,
2363 Medium,
2366 High,
2369}
2370
2371#[derive(Clone, Debug, PartialEq, Eq)]
2374#[non_exhaustive]
2375pub enum ResponseToolChoice {
2376 None,
2379 Auto,
2382 Required,
2385 HostedTool {
2388 tool_type: ResponseHostedToolChoice,
2391 },
2392 Function {
2395 name: String,
2398 },
2399 Custom {
2402 name: String,
2405 },
2406 Mcp {
2409 server_label: String,
2412 name: Option<String>,
2415 },
2416 ApplyPatch,
2419 Shell,
2422 AllowedTools {
2425 mode: ResponseAllowedToolsMode,
2428 tools: Vec<Value>,
2431 },
2432}
2433
2434impl ResponseToolChoice {
2435 pub fn hosted_tool(tool_type: ResponseHostedToolChoice) -> Self {
2438 Self::HostedTool { tool_type }
2439 }
2440
2441 pub fn function(name: impl Into<String>) -> Self {
2444 Self::Function { name: name.into() }
2445 }
2446
2447 pub fn custom(name: impl Into<String>) -> Self {
2450 Self::Custom { name: name.into() }
2451 }
2452
2453 pub fn mcp(server_label: impl Into<String>) -> Self {
2456 Self::Mcp {
2457 server_label: server_label.into(),
2458 name: None,
2459 }
2460 }
2461
2462 pub fn mcp_tool(server_label: impl Into<String>, name: impl Into<String>) -> Self {
2465 Self::Mcp {
2466 server_label: server_label.into(),
2467 name: Some(name.into()),
2468 }
2469 }
2470
2471 pub fn allowed_tools<I>(mode: ResponseAllowedToolsMode, tools: I) -> Self
2474 where
2475 I: IntoIterator<Item = Value>,
2476 {
2477 Self::AllowedTools {
2478 mode,
2479 tools: tools.into_iter().collect(),
2480 }
2481 }
2482}
2483
2484impl Serialize for ResponseToolChoice {
2485 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2486 where
2487 S: serde::Serializer,
2488 {
2489 match self {
2490 Self::None => serializer.serialize_str("none"),
2491 Self::Auto => serializer.serialize_str("auto"),
2492 Self::Required => serializer.serialize_str("required"),
2493 Self::HostedTool { tool_type } => {
2494 let mut state = serializer.serialize_struct("ResponseToolChoiceHostedTool", 1)?;
2495 state.serialize_field("type", tool_type)?;
2496 state.end()
2497 }
2498 Self::Function { name } => {
2499 let mut state = serializer.serialize_struct("ResponseToolChoiceFunction", 2)?;
2500 state.serialize_field("type", "function")?;
2501 state.serialize_field("name", name)?;
2502 state.end()
2503 }
2504 Self::Custom { name } => {
2505 let mut state = serializer.serialize_struct("ResponseToolChoiceCustom", 2)?;
2506 state.serialize_field("type", "custom")?;
2507 state.serialize_field("name", name)?;
2508 state.end()
2509 }
2510 Self::Mcp { server_label, name } => {
2511 let mut state = serializer
2512 .serialize_struct("ResponseToolChoiceMcp", 2 + usize::from(name.is_some()))?;
2513 state.serialize_field("type", "mcp")?;
2514 state.serialize_field("server_label", server_label)?;
2515 if let Some(name) = name {
2516 state.serialize_field("name", name)?;
2517 }
2518 state.end()
2519 }
2520 Self::ApplyPatch => {
2521 let mut state = serializer.serialize_struct("ResponseToolChoiceApplyPatch", 1)?;
2522 state.serialize_field("type", "apply_patch")?;
2523 state.end()
2524 }
2525 Self::Shell => {
2526 let mut state = serializer.serialize_struct("ResponseToolChoiceShell", 1)?;
2527 state.serialize_field("type", "shell")?;
2528 state.end()
2529 }
2530 Self::AllowedTools { mode, tools } => {
2531 let mut state = serializer.serialize_struct("ResponseToolChoiceAllowedTools", 3)?;
2532 state.serialize_field("type", "allowed_tools")?;
2533 state.serialize_field("mode", mode)?;
2534 state.serialize_field("tools", tools)?;
2535 state.end()
2536 }
2537 }
2538 }
2539}
2540
2541impl<'de> Deserialize<'de> for ResponseToolChoice {
2542 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2543 where
2544 D: serde::Deserializer<'de>,
2545 {
2546 #[derive(Deserialize)]
2547 struct ObjectToolChoice {
2548 #[serde(rename = "type")]
2549 tool_type: String,
2550 name: Option<String>,
2551 server_label: Option<String>,
2552 mode: Option<ResponseAllowedToolsMode>,
2553 tools: Option<Vec<Value>>,
2554 }
2555
2556 let value = Value::deserialize(deserializer)?;
2557 if let Some(value) = value.as_str() {
2558 return match value {
2559 "none" => Ok(Self::None),
2560 "auto" => Ok(Self::Auto),
2561 "required" => Ok(Self::Required),
2562 other => Err(serde::de::Error::unknown_variant(
2563 other,
2564 &["none", "auto", "required"],
2565 )),
2566 };
2567 }
2568 let object = ObjectToolChoice::deserialize(value).map_err(serde::de::Error::custom)?;
2569 if object.tool_type == "function" {
2570 let name = object
2571 .name
2572 .ok_or_else(|| serde::de::Error::missing_field("name"))?;
2573 return Ok(Self::Function { name });
2574 }
2575 if object.tool_type == "custom" {
2576 let name = object
2577 .name
2578 .ok_or_else(|| serde::de::Error::missing_field("name"))?;
2579 return Ok(Self::Custom { name });
2580 }
2581 if object.tool_type == "mcp" {
2582 let server_label = object
2583 .server_label
2584 .ok_or_else(|| serde::de::Error::missing_field("server_label"))?;
2585 return Ok(Self::Mcp {
2586 server_label,
2587 name: object.name,
2588 });
2589 }
2590 if object.tool_type == "apply_patch" {
2591 return Ok(Self::ApplyPatch);
2592 }
2593 if object.tool_type == "shell" {
2594 return Ok(Self::Shell);
2595 }
2596 if object.tool_type == "allowed_tools" {
2597 let mode = object
2598 .mode
2599 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
2600 let tools = object
2601 .tools
2602 .ok_or_else(|| serde::de::Error::missing_field("tools"))?;
2603 return Ok(Self::AllowedTools { mode, tools });
2604 }
2605 if let Some(tool_type) = ResponseHostedToolChoice::from_wire_type(&object.tool_type) {
2606 return Ok(Self::HostedTool { tool_type });
2607 }
2608 Err(serde::de::Error::unknown_variant(
2609 &object.tool_type,
2610 &[
2611 "none",
2612 "auto",
2613 "required",
2614 "file_search",
2615 "web_search_preview",
2616 "computer",
2617 "computer_use_preview",
2618 "computer_use",
2619 "web_search_preview_2025_03_11",
2620 "image_generation",
2621 "code_interpreter",
2622 "function",
2623 "custom",
2624 "mcp",
2625 "apply_patch",
2626 "shell",
2627 "allowed_tools",
2628 ],
2629 ))
2630 }
2631}
2632
2633#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2636#[serde(rename_all = "snake_case")]
2637#[non_exhaustive]
2638pub enum ResponseAllowedToolsMode {
2639 Auto,
2642 Required,
2645}
2646
2647#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2650#[non_exhaustive]
2651pub enum ResponseHostedToolChoice {
2652 #[serde(rename = "file_search")]
2655 FileSearch,
2656 #[serde(rename = "web_search_preview")]
2659 WebSearchPreview,
2660 #[serde(rename = "computer")]
2663 Computer,
2664 #[serde(rename = "computer_use_preview")]
2667 ComputerUsePreview,
2668 #[serde(rename = "computer_use")]
2671 ComputerUse,
2672 #[serde(rename = "web_search_preview_2025_03_11")]
2675 WebSearchPreview2025_03_11,
2676 #[serde(rename = "image_generation")]
2679 ImageGeneration,
2680 #[serde(rename = "code_interpreter")]
2683 CodeInterpreter,
2684}
2685
2686impl ResponseHostedToolChoice {
2687 fn from_wire_type(value: &str) -> Option<Self> {
2688 match value {
2689 "file_search" => Some(Self::FileSearch),
2690 "web_search_preview" => Some(Self::WebSearchPreview),
2691 "computer" => Some(Self::Computer),
2692 "computer_use_preview" => Some(Self::ComputerUsePreview),
2693 "computer_use" => Some(Self::ComputerUse),
2694 "web_search_preview_2025_03_11" => Some(Self::WebSearchPreview2025_03_11),
2695 "image_generation" => Some(Self::ImageGeneration),
2696 "code_interpreter" => Some(Self::CodeInterpreter),
2697 _ => None,
2698 }
2699 }
2700}
2701
2702#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
2705#[non_exhaustive]
2706pub struct ResponseReasoning {
2707 #[serde(skip_serializing_if = "Option::is_none")]
2710 pub effort: Option<ResponseReasoningEffort>,
2711 #[serde(skip_serializing_if = "Option::is_none")]
2714 pub summary: Option<ResponseReasoningSummary>,
2715}
2716
2717impl ResponseReasoning {
2718 pub fn builder() -> ResponseReasoningBuilder {
2721 ResponseReasoningBuilder::default()
2722 }
2723}
2724
2725#[derive(Clone, Debug, Default)]
2728#[non_exhaustive]
2729pub struct ResponseReasoningBuilder {
2730 effort: Option<ResponseReasoningEffort>,
2731 summary: Option<ResponseReasoningSummary>,
2732}
2733
2734impl ResponseReasoningBuilder {
2735 pub fn effort(mut self, effort: ResponseReasoningEffort) -> Self {
2738 self.effort = Some(effort);
2739 self
2740 }
2741
2742 pub fn summary(mut self, summary: ResponseReasoningSummary) -> Self {
2745 self.summary = Some(summary);
2746 self
2747 }
2748
2749 pub fn build(self) -> ResponseReasoning {
2752 ResponseReasoning {
2753 effort: self.effort,
2754 summary: self.summary,
2755 }
2756 }
2757}
2758
2759#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2762#[serde(rename_all = "snake_case")]
2763#[non_exhaustive]
2764pub enum ResponseReasoningEffort {
2765 None,
2768 Minimal,
2771 Low,
2774 Medium,
2777 High,
2780 #[serde(rename = "xhigh")]
2783 XHigh,
2784}
2785
2786#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2789#[serde(rename_all = "snake_case")]
2790#[non_exhaustive]
2791pub enum ResponseReasoningSummary {
2792 Auto,
2795 Concise,
2798 Detailed,
2801}
2802
2803#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2806#[serde(rename_all = "snake_case")]
2807#[non_exhaustive]
2808pub enum ResponseServiceTier {
2809 Auto,
2812 Default,
2815 Flex,
2818 Priority,
2821}
2822
2823#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
2826#[non_exhaustive]
2827pub enum ResponsePromptCacheRetention {
2828 #[serde(rename = "in_memory")]
2831 InMemory,
2832 #[serde(rename = "24h")]
2835 TwentyFourHours,
2836}
2837
2838#[derive(Clone, Debug, Default, Serialize, PartialEq)]
2841#[non_exhaustive]
2842pub struct CreateResponseInputTokensRequest {
2843 #[serde(skip_serializing_if = "Option::is_none")]
2846 pub model: Option<String>,
2847 #[serde(skip_serializing_if = "Option::is_none")]
2850 pub input: Option<ResponseInput>,
2851 #[serde(skip_serializing_if = "Option::is_none")]
2854 pub instructions: Option<String>,
2855 #[serde(skip_serializing_if = "Option::is_none")]
2858 pub previous_response_id: Option<String>,
2859 #[serde(skip_serializing_if = "Option::is_none")]
2862 pub text: Option<ResponseTextConfig>,
2863 #[serde(flatten)]
2866 pub extra: BTreeMap<String, Value>,
2867}
2868
2869impl CreateResponseInputTokensRequest {
2870 pub fn builder() -> CreateResponseInputTokensRequestBuilder {
2873 CreateResponseInputTokensRequestBuilder::default()
2874 }
2875}
2876
2877#[derive(Clone, Debug, Default)]
2880#[non_exhaustive]
2881pub struct CreateResponseInputTokensRequestBuilder {
2882 model: Option<String>,
2883 input: Option<ResponseInput>,
2884 instructions: Option<String>,
2885 previous_response_id: Option<String>,
2886 text: Option<ResponseTextConfig>,
2887 extra: BTreeMap<String, Value>,
2888}
2889
2890impl CreateResponseInputTokensRequestBuilder {
2891 pub fn model(mut self, model: impl Into<String>) -> Self {
2894 self.model = Some(model.into());
2895 self
2896 }
2897
2898 pub fn input(mut self, input: impl Into<ResponseInput>) -> Self {
2901 self.input = Some(input.into());
2902 self
2903 }
2904
2905 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
2908 self.instructions = Some(instructions.into());
2909 self
2910 }
2911
2912 pub fn previous_response_id(mut self, previous_response_id: impl Into<String>) -> Self {
2915 self.previous_response_id = Some(previous_response_id.into());
2916 self
2917 }
2918
2919 pub fn text(mut self, text: ResponseTextConfig) -> Self {
2922 self.text = Some(text);
2923 self
2924 }
2925
2926 pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
2929 self.extra.insert(name.into(), value);
2930 self
2931 }
2932
2933 pub fn build(self) -> Result<CreateResponseInputTokensRequest, LingerError> {
2936 validate_optional_string("model", self.model.as_deref())?;
2937 validate_optional_string("instructions", self.instructions.as_deref())?;
2938 validate_optional_string("previous_response_id", self.previous_response_id.as_deref())?;
2939 if self.input.is_none() && self.extra.is_empty() {
2940 return Err(LingerError::invalid_config("input is required"));
2941 }
2942 if let Some(text) = &self.text {
2943 validate_text_config(text)?;
2944 }
2945 validate_extra_fields(&self.extra)?;
2946 Ok(CreateResponseInputTokensRequest {
2947 model: self.model,
2948 input: self.input,
2949 instructions: self.instructions,
2950 previous_response_id: self.previous_response_id,
2951 text: self.text,
2952 extra: self.extra,
2953 })
2954 }
2955}
2956
2957#[derive(Clone, Debug, Serialize, PartialEq)]
2960#[non_exhaustive]
2961pub struct CompactResponseRequest {
2962 pub model: String,
2965 #[serde(skip_serializing_if = "Option::is_none")]
2968 pub input: Option<ResponseInput>,
2969 #[serde(skip_serializing_if = "Option::is_none")]
2972 pub instructions: Option<String>,
2973 #[serde(skip_serializing_if = "Option::is_none")]
2976 pub previous_response_id: Option<String>,
2977 #[serde(flatten)]
2980 pub extra: BTreeMap<String, Value>,
2981}
2982
2983impl CompactResponseRequest {
2984 pub fn builder() -> CompactResponseRequestBuilder {
2987 CompactResponseRequestBuilder::default()
2988 }
2989}
2990
2991#[derive(Clone, Debug, Default)]
2994#[non_exhaustive]
2995pub struct CompactResponseRequestBuilder {
2996 model: Option<String>,
2997 input: Option<ResponseInput>,
2998 instructions: Option<String>,
2999 previous_response_id: Option<String>,
3000 extra: BTreeMap<String, Value>,
3001}
3002
3003impl CompactResponseRequestBuilder {
3004 pub fn model(mut self, model: impl Into<String>) -> Self {
3007 self.model = Some(model.into());
3008 self
3009 }
3010
3011 pub fn input(mut self, input: impl Into<ResponseInput>) -> Self {
3014 self.input = Some(input.into());
3015 self
3016 }
3017
3018 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
3021 self.instructions = Some(instructions.into());
3022 self
3023 }
3024
3025 pub fn previous_response_id(mut self, previous_response_id: impl Into<String>) -> Self {
3028 self.previous_response_id = Some(previous_response_id.into());
3029 self
3030 }
3031
3032 pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
3035 self.extra.insert(name.into(), value);
3036 self
3037 }
3038
3039 pub fn build(self) -> Result<CompactResponseRequest, LingerError> {
3042 let model = self
3043 .model
3044 .filter(|value| !value.trim().is_empty())
3045 .ok_or_else(|| LingerError::invalid_config("model is required"))?;
3046 validate_optional_string("instructions", self.instructions.as_deref())?;
3047 validate_optional_string("previous_response_id", self.previous_response_id.as_deref())?;
3048 validate_extra_fields(&self.extra)?;
3049 Ok(CompactResponseRequest {
3050 model,
3051 input: self.input,
3052 instructions: self.instructions,
3053 previous_response_id: self.previous_response_id,
3054 extra: self.extra,
3055 })
3056 }
3057}
3058
3059#[derive(Clone, Debug, Serialize, PartialEq)]
3062#[serde(untagged)]
3063#[non_exhaustive]
3064pub enum ResponseInput {
3065 Text(String),
3068 Messages(Vec<ResponseInputMessage>),
3071}
3072
3073impl From<&str> for ResponseInput {
3074 fn from(value: &str) -> Self {
3075 Self::Text(value.to_string())
3076 }
3077}
3078
3079impl From<String> for ResponseInput {
3080 fn from(value: String) -> Self {
3081 Self::Text(value)
3082 }
3083}
3084
3085impl From<Vec<ResponseInputMessage>> for ResponseInput {
3086 fn from(value: Vec<ResponseInputMessage>) -> Self {
3087 Self::Messages(value)
3088 }
3089}
3090
3091#[derive(Clone, Debug, Serialize, PartialEq)]
3094#[non_exhaustive]
3095pub struct ResponseInputMessage {
3096 pub role: String,
3099 pub content: Vec<ResponseInputMessageContent>,
3102}
3103
3104#[derive(Clone, Debug, Serialize, PartialEq)]
3107#[serde(tag = "type")]
3108#[non_exhaustive]
3109pub enum ResponseInputMessageContent {
3110 #[serde(rename = "input_text")]
3113 InputText {
3114 text: String,
3117 },
3118}
3119
3120#[derive(Clone, Debug, Default, Serialize, PartialEq)]
3123#[non_exhaustive]
3124pub struct ResponseTextConfig {
3125 #[serde(skip_serializing_if = "Option::is_none")]
3128 pub format: Option<ResponseTextFormat>,
3129 #[serde(skip_serializing_if = "Option::is_none")]
3132 pub verbosity: Option<ResponseTextVerbosity>,
3133}
3134
3135impl ResponseTextConfig {
3136 pub fn format(mut self, format: impl Into<ResponseTextFormat>) -> Self {
3139 self.format = Some(format.into());
3140 self
3141 }
3142
3143 pub fn verbosity(mut self, verbosity: ResponseTextVerbosity) -> Self {
3146 self.verbosity = Some(verbosity);
3147 self
3148 }
3149}
3150
3151#[derive(Clone, Debug, Serialize, PartialEq)]
3154#[serde(tag = "type")]
3155#[non_exhaustive]
3156pub enum ResponseTextFormat {
3157 #[serde(rename = "text")]
3160 Text,
3161 #[serde(rename = "json_object")]
3164 JsonObject,
3165 #[serde(rename = "json_schema")]
3168 JsonSchema {
3169 name: String,
3172 schema: Value,
3175 #[serde(skip_serializing_if = "Option::is_none")]
3178 description: Option<String>,
3179 #[serde(skip_serializing_if = "Option::is_none")]
3182 strict: Option<bool>,
3183 },
3184}
3185
3186impl ResponseTextFormat {
3187 pub fn json_schema(name: impl Into<String>, schema: Value) -> ResponseTextJsonSchemaFormat {
3190 ResponseTextJsonSchemaFormat::new(name, schema)
3191 }
3192}
3193
3194#[derive(Clone, Debug, PartialEq)]
3197#[non_exhaustive]
3198pub struct ResponseTextJsonSchemaFormat {
3199 pub name: String,
3202 pub schema: Value,
3205 pub description: Option<String>,
3208 pub strict: Option<bool>,
3211}
3212
3213impl ResponseTextJsonSchemaFormat {
3214 pub fn new(name: impl Into<String>, schema: Value) -> Self {
3217 Self {
3218 name: name.into(),
3219 schema,
3220 description: None,
3221 strict: None,
3222 }
3223 }
3224
3225 pub fn description(mut self, description: impl Into<String>) -> Self {
3228 self.description = Some(description.into());
3229 self
3230 }
3231
3232 pub fn strict(mut self, strict: bool) -> Self {
3235 self.strict = Some(strict);
3236 self
3237 }
3238}
3239
3240impl From<ResponseTextJsonSchemaFormat> for ResponseTextFormat {
3241 fn from(format: ResponseTextJsonSchemaFormat) -> Self {
3242 Self::JsonSchema {
3243 name: format.name,
3244 schema: format.schema,
3245 description: format.description,
3246 strict: format.strict,
3247 }
3248 }
3249}
3250
3251#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
3254#[serde(rename_all = "snake_case")]
3255#[non_exhaustive]
3256pub enum ResponseTextVerbosity {
3257 Low,
3260 Medium,
3263 High,
3266}
3267
3268#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
3271#[non_exhaustive]
3272pub struct StreamOptions {
3273 #[serde(skip_serializing_if = "Option::is_none")]
3276 pub include_obfuscation: Option<bool>,
3277}
3278
3279impl StreamOptions {
3280 pub fn builder() -> StreamOptionsBuilder {
3283 StreamOptionsBuilder::default()
3284 }
3285}
3286
3287#[derive(Clone, Debug, Default)]
3290#[non_exhaustive]
3291pub struct StreamOptionsBuilder {
3292 include_obfuscation: Option<bool>,
3293}
3294
3295impl StreamOptionsBuilder {
3296 pub fn include_obfuscation(mut self, include_obfuscation: bool) -> Self {
3299 self.include_obfuscation = Some(include_obfuscation);
3300 self
3301 }
3302
3303 pub fn build(self) -> StreamOptions {
3306 StreamOptions {
3307 include_obfuscation: self.include_obfuscation,
3308 }
3309 }
3310}
3311
3312#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3315#[non_exhaustive]
3316pub struct Response {
3317 pub id: String,
3320 pub object: String,
3323 pub model: String,
3326 #[serde(default)]
3329 pub output: Vec<ResponseOutput>,
3330 #[serde(skip)]
3333 request_id: Option<RequestId>,
3334}
3335
3336impl Response {
3337 pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
3338 self.request_id = request_id;
3339 self
3340 }
3341
3342 pub fn request_id(&self) -> Option<&RequestId> {
3345 self.request_id.as_ref()
3346 }
3347
3348 pub fn output_text(&self) -> String {
3351 let mut text = String::new();
3352 for item in &self.output {
3353 if let ResponseOutput::Message(message) = item {
3354 for content in &message.content {
3355 if let ResponseContent::OutputText { text: value, .. } = content {
3356 text.push_str(value);
3357 }
3358 }
3359 }
3360 }
3361 text
3362 }
3363}
3364
3365#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
3368#[non_exhaustive]
3369pub struct ResponseInputTokens {
3370 pub object: String,
3373 pub input_tokens: u64,
3376 #[serde(skip)]
3379 request_id: Option<RequestId>,
3380}
3381
3382impl ResponseInputTokens {
3383 pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
3384 self.request_id = request_id;
3385 self
3386 }
3387
3388 pub fn request_id(&self) -> Option<&RequestId> {
3391 self.request_id.as_ref()
3392 }
3393}
3394
3395#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3398#[non_exhaustive]
3399pub struct ResponseCompaction {
3400 pub id: String,
3403 pub object: String,
3406 pub created_at: u64,
3409 #[serde(default)]
3412 pub output: Vec<Value>,
3413 pub usage: ResponseUsage,
3416 #[serde(flatten)]
3419 pub extra: BTreeMap<String, Value>,
3420 #[serde(skip)]
3423 request_id: Option<RequestId>,
3424}
3425
3426impl ResponseCompaction {
3427 pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
3428 self.request_id = request_id;
3429 self
3430 }
3431
3432 pub fn request_id(&self) -> Option<&RequestId> {
3435 self.request_id.as_ref()
3436 }
3437}
3438
3439#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
3442#[non_exhaustive]
3443pub struct ResponseUsage {
3444 pub input_tokens: u64,
3447 pub output_tokens: u64,
3450 pub total_tokens: u64,
3453 #[serde(default)]
3456 pub input_tokens_details: BTreeMap<String, Value>,
3457 #[serde(default)]
3460 pub output_tokens_details: BTreeMap<String, Value>,
3461 #[serde(flatten)]
3464 pub extra: BTreeMap<String, Value>,
3465}
3466
3467#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
3470#[non_exhaustive]
3471pub struct ResponseDeletion {
3472 pub id: String,
3475 pub object: String,
3478 pub deleted: bool,
3481 #[serde(skip)]
3484 request_id: Option<RequestId>,
3485}
3486
3487impl ResponseDeletion {
3488 pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
3489 self.request_id = request_id;
3490 self
3491 }
3492
3493 pub fn request_id(&self) -> Option<&RequestId> {
3496 self.request_id.as_ref()
3497 }
3498}
3499
3500#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3503#[non_exhaustive]
3504pub struct ResponseInputItemsPage {
3505 pub object: String,
3508 #[serde(default)]
3511 pub data: Vec<ResponseInputItem>,
3512 #[serde(default)]
3515 pub first_id: Option<String>,
3516 #[serde(default)]
3519 pub last_id: Option<String>,
3520 pub has_more: bool,
3523 #[serde(skip)]
3526 request_id: Option<RequestId>,
3527}
3528
3529impl ResponseInputItemsPage {
3530 pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
3531 self.request_id = request_id;
3532 self
3533 }
3534
3535 pub fn request_id(&self) -> Option<&RequestId> {
3538 self.request_id.as_ref()
3539 }
3540}
3541
3542#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3545#[serde(tag = "type")]
3546#[non_exhaustive]
3547pub enum ResponseInputItem {
3548 #[serde(rename = "message")]
3551 Message(ResponseInputItemMessage),
3552 #[serde(other)]
3555 Unknown,
3556}
3557
3558#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3561#[non_exhaustive]
3562pub struct ResponseInputItemMessage {
3563 pub id: String,
3566 pub role: String,
3569 #[serde(default)]
3572 pub content: Vec<ResponseInputItemContent>,
3573}
3574
3575impl ResponseInputItemMessage {
3576 pub fn input_text(&self) -> String {
3579 let mut text = String::new();
3580 for content in &self.content {
3581 if let ResponseInputItemContent::InputText { text: value, .. } = content {
3582 text.push_str(value);
3583 }
3584 }
3585 text
3586 }
3587}
3588
3589#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3592#[serde(tag = "type")]
3593#[non_exhaustive]
3594pub enum ResponseInputItemContent {
3595 #[serde(rename = "input_text")]
3598 InputText {
3599 text: String,
3602 #[serde(flatten)]
3605 extra: BTreeMap<String, Value>,
3606 },
3607 #[serde(other)]
3610 Unknown,
3611}
3612
3613#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3616#[serde(tag = "type")]
3617#[non_exhaustive]
3618pub enum ResponseOutput {
3619 #[serde(rename = "message")]
3622 Message(ResponseOutputMessage),
3623 #[serde(other)]
3626 Unknown,
3627}
3628
3629#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3632#[non_exhaustive]
3633pub struct ResponseOutputMessage {
3634 pub id: String,
3637 pub role: String,
3640 #[serde(default)]
3643 pub content: Vec<ResponseContent>,
3644}
3645
3646#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3649#[serde(tag = "type")]
3650#[non_exhaustive]
3651pub enum ResponseContent {
3652 #[serde(rename = "output_text")]
3655 OutputText {
3656 text: String,
3659 #[serde(flatten)]
3662 extra: BTreeMap<String, Value>,
3663 },
3664 #[serde(other)]
3667 Unknown,
3668}
3669
3670#[derive(Clone, Debug, PartialEq)]
3673#[non_exhaustive]
3674pub enum ResponseStreamEvent {
3675 OutputTextDelta {
3678 delta: String,
3681 },
3682 Completed {
3685 response: Response,
3688 },
3689 Unknown {
3692 event_type: String,
3695 data: Value,
3698 },
3699}
3700
3701#[derive(Clone, Debug, PartialEq)]
3704#[non_exhaustive]
3705pub struct ResponseStreamItem {
3706 pub event: ResponseStreamEvent,
3709 pub raw: SseEvent,
3712}
3713
3714impl ResponseStreamItem {
3715 pub fn event_type(&self) -> &str {
3718 self.raw.event_type.as_deref().unwrap_or("")
3719 }
3720
3721 pub fn output_text_delta(&self) -> Option<&str> {
3724 match &self.event {
3725 ResponseStreamEvent::OutputTextDelta { delta } => Some(delta),
3726 _ => None,
3727 }
3728 }
3729}
3730
3731pub struct ResponseStream {
3734 inner: SseStream,
3735}
3736
3737impl ResponseStream {
3738 pub fn new(body: BodyStream) -> Self {
3741 Self {
3742 inner: SseStream::new(body),
3743 }
3744 }
3745}
3746
3747impl Stream for ResponseStream {
3748 type Item = Result<ResponseStreamItem, LingerError>;
3749
3750 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
3751 let this = self.get_mut();
3752 match Pin::new(&mut this.inner).poll_next(cx) {
3753 Poll::Ready(Some(Ok(raw))) => Poll::Ready(Some(parse_response_event(raw))),
3754 Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(error))),
3755 Poll::Ready(None) => Poll::Ready(None),
3756 Poll::Pending => Poll::Pending,
3757 }
3758 }
3759}
3760
3761fn parse_response_event(raw: SseEvent) -> Result<ResponseStreamItem, LingerError> {
3762 let event_type = raw.event_type.clone().unwrap_or_default();
3763 let value: Value = serde_json::from_str(&raw.data).map_err(|error| {
3764 LingerError::streaming(format!("invalid response stream JSON: {error}"))
3765 })?;
3766 let event = match event_type.as_str() {
3767 "response.output_text.delta" => {
3768 let delta = value
3769 .get("delta")
3770 .and_then(Value::as_str)
3771 .unwrap_or_default()
3772 .to_string();
3773 ResponseStreamEvent::OutputTextDelta { delta }
3774 }
3775 "response.completed" => {
3776 let response = value.get("response").cloned().ok_or_else(|| {
3777 LingerError::streaming("response.completed event is missing response")
3778 })?;
3779 let response = serde_json::from_value(response).map_err(|error| {
3780 LingerError::streaming(format!("invalid completed response: {error}"))
3781 })?;
3782 ResponseStreamEvent::Completed { response }
3783 }
3784 _ => ResponseStreamEvent::Unknown {
3785 event_type,
3786 data: value,
3787 },
3788 };
3789 Ok(ResponseStreamItem { event, raw })
3790}
3791
3792fn validate_optional_string(name: &str, value: Option<&str>) -> Result<(), LingerError> {
3793 if value.is_some_and(|value| value.trim().is_empty()) {
3794 return Err(LingerError::invalid_config(format!(
3795 "{name} must not be empty"
3796 )));
3797 }
3798 Ok(())
3799}
3800
3801fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
3802 if metadata.len() > 16 {
3803 return Err(LingerError::invalid_config(
3804 "metadata must contain at most 16 entries",
3805 ));
3806 }
3807 for (key, value) in metadata {
3808 if key.trim().is_empty() {
3809 return Err(LingerError::invalid_config(
3810 "metadata keys must not be empty",
3811 ));
3812 }
3813 if key.chars().count() > 64 {
3814 return Err(LingerError::invalid_config(
3815 "metadata keys must be at most 64 characters",
3816 ));
3817 }
3818 if value.chars().count() > 512 {
3819 return Err(LingerError::invalid_config(
3820 "metadata values must be at most 512 characters",
3821 ));
3822 }
3823 }
3824 Ok(())
3825}
3826
3827fn validate_json_items(name: &str, values: &[Value]) -> Result<(), LingerError> {
3828 if values.iter().any(Value::is_null) {
3829 return Err(LingerError::invalid_config(format!(
3830 "{name} must not contain null"
3831 )));
3832 }
3833 Ok(())
3834}
3835
3836fn validate_tools(tools: &[Value]) -> Result<(), LingerError> {
3837 validate_json_items("tools", tools)?;
3838 for tool in tools {
3839 let Some(object) = tool.as_object() else {
3840 continue;
3841 };
3842 match object.get("type").and_then(Value::as_str) {
3843 Some("function") => validate_function_tool(object)?,
3844 Some("custom") => validate_custom_tool(object)?,
3845 Some("namespace") => validate_namespace_tool(object)?,
3846 Some("tool_search") => validate_tool_search_tool(object)?,
3847 Some("shell") => validate_shell_tool(object)?,
3848 Some("file_search") => validate_file_search_tool(object)?,
3849 Some("computer_use_preview") => validate_computer_use_preview_tool(object)?,
3850 Some("code_interpreter") => validate_code_interpreter_tool(object)?,
3851 Some("image_generation") => validate_image_generation_tool(object)?,
3852 Some("mcp") => validate_mcp_tool(object)?,
3853 Some(
3854 "web_search"
3855 | "web_search_2025_08_26"
3856 | "web_search_preview"
3857 | "web_search_preview_2025_03_11",
3858 ) => validate_web_search_tool(object)?,
3859 _ => {}
3860 }
3861 }
3862 Ok(())
3863}
3864
3865fn validate_custom_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
3866 let name = object
3867 .get("name")
3868 .and_then(Value::as_str)
3869 .unwrap_or_default();
3870 validate_optional_string("custom tool name", Some(name))?;
3871 validate_optional_string(
3872 "custom tool description",
3873 object.get("description").and_then(Value::as_str),
3874 )?;
3875 if let Some(description) = object.get("description") {
3876 if !description.is_string() {
3877 return Err(LingerError::invalid_config(
3878 "custom tool description must be a string",
3879 ));
3880 }
3881 }
3882 if let Some(defer_loading) = object.get("defer_loading") {
3883 if !defer_loading.is_boolean() {
3884 return Err(LingerError::invalid_config(
3885 "custom tool defer_loading must be a boolean",
3886 ));
3887 }
3888 }
3889 if let Some(format) = object.get("format") {
3890 validate_custom_tool_format(format)?;
3891 }
3892 Ok(())
3893}
3894
3895fn validate_custom_tool_format(format: &Value) -> Result<(), LingerError> {
3896 let Some(format) = format.as_object() else {
3897 return Err(LingerError::invalid_config(
3898 "custom tool format must be a JSON object",
3899 ));
3900 };
3901 match format.get("type").and_then(Value::as_str) {
3902 Some("text") => Ok(()),
3903 Some("grammar") => {
3904 let syntax = format
3905 .get("syntax")
3906 .and_then(Value::as_str)
3907 .ok_or_else(|| {
3908 LingerError::invalid_config("custom tool grammar format must include syntax")
3909 })?;
3910 if !matches!(syntax, "lark" | "regex") {
3911 return Err(LingerError::invalid_config(
3912 "custom tool grammar syntax has an unsupported value",
3913 ));
3914 }
3915 let definition = format
3916 .get("definition")
3917 .and_then(Value::as_str)
3918 .unwrap_or_default();
3919 validate_optional_string("custom tool grammar definition", Some(definition))
3920 }
3921 _ => Err(LingerError::invalid_config(
3922 "custom tool format type has an unsupported value",
3923 )),
3924 }
3925}
3926
3927fn validate_tool_search_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
3928 if let Some(execution) = object.get("execution") {
3929 let Some(execution) = execution.as_str() else {
3930 return Err(LingerError::invalid_config(
3931 "tool_search execution must be a string",
3932 ));
3933 };
3934 if !matches!(execution, "server" | "client") {
3935 return Err(LingerError::invalid_config(
3936 "tool_search execution has an unsupported value",
3937 ));
3938 }
3939 }
3940 if let Some(description) = object.get("description") {
3941 if !(description.is_string() || description.is_null()) {
3942 return Err(LingerError::invalid_config(
3943 "tool_search description must be a string or null",
3944 ));
3945 }
3946 if let Some(description) = description.as_str() {
3947 validate_optional_string("tool_search description", Some(description))?;
3948 }
3949 }
3950 if let Some(parameters) = object.get("parameters") {
3951 if !(parameters.is_object() || parameters.is_null()) {
3952 return Err(LingerError::invalid_config(
3953 "tool_search parameters must be a JSON object or null",
3954 ));
3955 }
3956 }
3957 Ok(())
3958}
3959
3960fn validate_shell_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
3961 let Some(environment) = object.get("environment") else {
3962 return Ok(());
3963 };
3964 if environment.is_null() {
3965 return Ok(());
3966 }
3967 let Some(environment) = environment.as_object() else {
3968 return Err(LingerError::invalid_config(
3969 "shell environment must be a JSON object or null",
3970 ));
3971 };
3972 match environment.get("type").and_then(Value::as_str) {
3973 Some("local" | "container_auto") => Ok(()),
3974 Some("container_reference") => {
3975 let container_id = environment
3976 .get("container_id")
3977 .and_then(Value::as_str)
3978 .unwrap_or_default();
3979 validate_optional_string("shell container_id", Some(container_id))
3980 }
3981 _ => Err(LingerError::invalid_config(
3982 "shell environment type has an unsupported value",
3983 )),
3984 }
3985}
3986
3987fn validate_namespace_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
3988 let name = object
3989 .get("name")
3990 .and_then(Value::as_str)
3991 .unwrap_or_default();
3992 validate_optional_string("namespace tool name", Some(name))?;
3993 let description = object
3994 .get("description")
3995 .and_then(Value::as_str)
3996 .unwrap_or_default();
3997 validate_optional_string("namespace tool description", Some(description))?;
3998 let tools = object
3999 .get("tools")
4000 .and_then(Value::as_array)
4001 .ok_or_else(|| LingerError::invalid_config("namespace tools must include tools"))?;
4002 if tools.is_empty() {
4003 return Err(LingerError::invalid_config(
4004 "namespace tools must not be empty",
4005 ));
4006 }
4007 for tool in tools {
4008 let Some(tool) = tool.as_object() else {
4009 return Err(LingerError::invalid_config(
4010 "namespace tools must contain objects",
4011 ));
4012 };
4013 match tool.get("type").and_then(Value::as_str) {
4014 Some("function") => validate_namespace_function_tool(tool)?,
4015 Some("custom") => validate_custom_tool(tool)?,
4016 _ => {
4017 return Err(LingerError::invalid_config(
4018 "namespace tools may only contain function or custom tools",
4019 ));
4020 }
4021 }
4022 }
4023 Ok(())
4024}
4025
4026fn validate_namespace_function_tool(
4027 object: &serde_json::Map<String, Value>,
4028) -> Result<(), LingerError> {
4029 let name = object
4030 .get("name")
4031 .and_then(Value::as_str)
4032 .unwrap_or_default();
4033 validate_optional_string("namespace function tool name", Some(name))?;
4034 if let Some(description) = object.get("description") {
4035 if !(description.is_string() || description.is_null()) {
4036 return Err(LingerError::invalid_config(
4037 "namespace function tool description must be a string or null",
4038 ));
4039 }
4040 }
4041 if let Some(parameters) = object.get("parameters") {
4042 if !(parameters.is_object() || parameters.is_null()) {
4043 return Err(LingerError::invalid_config(
4044 "namespace function tool parameters must be a JSON object or null",
4045 ));
4046 }
4047 }
4048 if let Some(strict) = object.get("strict") {
4049 if !(strict.is_boolean() || strict.is_null()) {
4050 return Err(LingerError::invalid_config(
4051 "namespace function tool strict must be a boolean or null",
4052 ));
4053 }
4054 }
4055 if let Some(defer_loading) = object.get("defer_loading") {
4056 if !defer_loading.is_boolean() {
4057 return Err(LingerError::invalid_config(
4058 "namespace function tool defer_loading must be a boolean",
4059 ));
4060 }
4061 }
4062 Ok(())
4063}
4064
4065fn validate_computer_use_preview_tool(
4066 object: &serde_json::Map<String, Value>,
4067) -> Result<(), LingerError> {
4068 let environment = object
4069 .get("environment")
4070 .and_then(Value::as_str)
4071 .ok_or_else(|| {
4072 LingerError::invalid_config("computer_use_preview tools must include environment")
4073 })?;
4074 if !matches!(
4075 environment,
4076 "windows" | "mac" | "linux" | "ubuntu" | "browser"
4077 ) {
4078 return Err(LingerError::invalid_config(
4079 "computer_use_preview environment has an unsupported value",
4080 ));
4081 }
4082
4083 for field in ["display_width", "display_height"] {
4084 let value = object.get(field).and_then(Value::as_u64).ok_or_else(|| {
4085 LingerError::invalid_config(format!(
4086 "computer_use_preview tools must include integer {field}"
4087 ))
4088 })?;
4089 if value == 0 {
4090 return Err(LingerError::invalid_config(format!(
4091 "computer_use_preview {field} must be greater than 0"
4092 )));
4093 }
4094 }
4095 Ok(())
4096}
4097
4098fn validate_function_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
4099 let name = object
4100 .get("name")
4101 .and_then(Value::as_str)
4102 .unwrap_or_default();
4103 validate_optional_string("tools[].name", Some(name))?;
4104 let parameters = object
4105 .get("parameters")
4106 .ok_or_else(|| LingerError::invalid_config("function tools must include parameters"))?;
4107 if parameters.is_null() {
4108 return Err(LingerError::invalid_config(
4109 "function tool parameters must not be null",
4110 ));
4111 }
4112 if !parameters.is_object() {
4113 return Err(LingerError::invalid_config(
4114 "function tool parameters must be a JSON object",
4115 ));
4116 }
4117 if !object.get("strict").is_some_and(Value::is_boolean) {
4118 return Err(LingerError::invalid_config(
4119 "function tools must include boolean strict",
4120 ));
4121 }
4122 Ok(())
4123}
4124
4125fn validate_file_search_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
4126 let vector_store_ids = object
4127 .get("vector_store_ids")
4128 .and_then(Value::as_array)
4129 .ok_or_else(|| {
4130 LingerError::invalid_config("file_search tools must include vector_store_ids")
4131 })?;
4132 if vector_store_ids.is_empty() {
4133 return Err(LingerError::invalid_config(
4134 "file_search vector_store_ids must not be empty",
4135 ));
4136 }
4137 for vector_store_id in vector_store_ids {
4138 let Some(vector_store_id) = vector_store_id.as_str() else {
4139 return Err(LingerError::invalid_config(
4140 "file_search vector_store_ids must contain strings",
4141 ));
4142 };
4143 validate_optional_string("file_search vector_store_ids", Some(vector_store_id))?;
4144 }
4145 if let Some(max_num_results) = object.get("max_num_results") {
4146 let max_num_results = max_num_results.as_u64().ok_or_else(|| {
4147 LingerError::invalid_config("file_search max_num_results must be an integer")
4148 })?;
4149 if !(1..=50).contains(&max_num_results) {
4150 return Err(LingerError::invalid_config(
4151 "file_search max_num_results must be between 1 and 50",
4152 ));
4153 }
4154 }
4155 for field in ["ranking_options", "filters"] {
4156 if object.get(field).is_some_and(Value::is_null) {
4157 return Err(LingerError::invalid_config(format!(
4158 "file_search {field} must not be null"
4159 )));
4160 }
4161 }
4162 Ok(())
4163}
4164
4165fn validate_code_interpreter_tool(
4166 object: &serde_json::Map<String, Value>,
4167) -> Result<(), LingerError> {
4168 let container = object.get("container").ok_or_else(|| {
4169 LingerError::invalid_config("code_interpreter tools must include container")
4170 })?;
4171 if container.is_null() {
4172 return Err(LingerError::invalid_config(
4173 "code_interpreter container must not be null",
4174 ));
4175 }
4176 if let Some(container_id) = container.as_str() {
4177 validate_optional_string("code_interpreter container", Some(container_id))?;
4178 return Ok(());
4179 }
4180 let container = container.as_object().ok_or_else(|| {
4181 LingerError::invalid_config("code_interpreter container must be a string or object")
4182 })?;
4183 let container_type = container
4184 .get("type")
4185 .and_then(Value::as_str)
4186 .unwrap_or_default();
4187 if container_type != "auto" {
4188 return Err(LingerError::invalid_config(
4189 "code_interpreter auto container type must be auto",
4190 ));
4191 }
4192 if let Some(file_ids) = container.get("file_ids") {
4193 let file_ids = file_ids.as_array().ok_or_else(|| {
4194 LingerError::invalid_config("code_interpreter file_ids must be an array")
4195 })?;
4196 if file_ids.len() > 50 {
4197 return Err(LingerError::invalid_config(
4198 "code_interpreter file_ids must contain at most 50 entries",
4199 ));
4200 }
4201 for file_id in file_ids {
4202 let Some(file_id) = file_id.as_str() else {
4203 return Err(LingerError::invalid_config(
4204 "code_interpreter file_ids must contain strings",
4205 ));
4206 };
4207 validate_optional_string("code_interpreter file_ids", Some(file_id))?;
4208 }
4209 }
4210 if let Some(memory_limit) = container.get("memory_limit") {
4211 if !memory_limit.is_null() {
4212 let Some(memory_limit) = memory_limit.as_str() else {
4213 return Err(LingerError::invalid_config(
4214 "code_interpreter memory_limit must be a string",
4215 ));
4216 };
4217 if !matches!(memory_limit, "1g" | "4g" | "16g" | "64g") {
4218 return Err(LingerError::invalid_config(
4219 "code_interpreter memory_limit must be one of 1g, 4g, 16g, or 64g",
4220 ));
4221 }
4222 }
4223 }
4224 if let Some(network_policy) = container.get("network_policy") {
4225 validate_code_interpreter_network_policy(network_policy)?;
4226 }
4227 Ok(())
4228}
4229
4230fn validate_code_interpreter_network_policy(network_policy: &Value) -> Result<(), LingerError> {
4231 if network_policy.is_null() {
4232 return Err(LingerError::invalid_config(
4233 "code_interpreter network_policy must not be null",
4234 ));
4235 }
4236 let policy = network_policy.as_object().ok_or_else(|| {
4237 LingerError::invalid_config("code_interpreter network_policy must be a JSON object")
4238 })?;
4239 match policy.get("type").and_then(Value::as_str) {
4240 Some("disabled") => Ok(()),
4241 Some("allowlist") => {
4242 if let Some(domains) = policy.get("allowed_domains") {
4243 let domains = domains.as_array().ok_or_else(|| {
4244 LingerError::invalid_config("code_interpreter allowed_domains must be an array")
4245 })?;
4246 if domains.is_empty() {
4247 return Err(LingerError::invalid_config(
4248 "code_interpreter allowed_domains must not be empty",
4249 ));
4250 }
4251 for domain in domains {
4252 let Some(domain) = domain.as_str() else {
4253 return Err(LingerError::invalid_config(
4254 "code_interpreter allowed_domains must contain strings",
4255 ));
4256 };
4257 validate_optional_string("code_interpreter allowed_domains", Some(domain))?;
4258 }
4259 }
4260 Ok(())
4261 }
4262 _ => Err(LingerError::invalid_config(
4263 "code_interpreter network_policy type must be disabled or allowlist",
4264 )),
4265 }
4266}
4267
4268fn validate_image_generation_tool(
4269 object: &serde_json::Map<String, Value>,
4270) -> Result<(), LingerError> {
4271 for field in ["model", "size"] {
4272 if let Some(value) = object.get(field) {
4273 let Some(value) = value.as_str() else {
4274 return Err(LingerError::invalid_config(format!(
4275 "image_generation {field} must be a string"
4276 )));
4277 };
4278 validate_optional_string(&format!("image_generation {field}"), Some(value))?;
4279 }
4280 }
4281 validate_image_generation_enum(object, "quality", &["low", "medium", "high", "auto"])?;
4282 validate_image_generation_enum(object, "output_format", &["png", "webp", "jpeg"])?;
4283 validate_image_generation_enum(object, "moderation", &["auto", "low"])?;
4284 validate_image_generation_enum(object, "background", &["transparent", "opaque", "auto"])?;
4285 validate_image_generation_enum(object, "input_fidelity", &["high", "low"])?;
4286 validate_image_generation_enum(object, "action", &["generate", "edit", "auto"])?;
4287 if let Some(output_compression) = object.get("output_compression") {
4288 let output_compression = output_compression.as_u64().ok_or_else(|| {
4289 LingerError::invalid_config("image_generation output_compression must be an integer")
4290 })?;
4291 if output_compression > 100 {
4292 return Err(LingerError::invalid_config(
4293 "image_generation output_compression must be between 0 and 100",
4294 ));
4295 }
4296 }
4297 if let Some(partial_images) = object.get("partial_images") {
4298 let partial_images = partial_images.as_u64().ok_or_else(|| {
4299 LingerError::invalid_config("image_generation partial_images must be an integer")
4300 })?;
4301 if partial_images > 3 {
4302 return Err(LingerError::invalid_config(
4303 "image_generation partial_images must be between 0 and 3",
4304 ));
4305 }
4306 }
4307 if let Some(input_image_mask) = object.get("input_image_mask") {
4308 validate_image_generation_mask(input_image_mask)?;
4309 }
4310 Ok(())
4311}
4312
4313fn validate_image_generation_enum(
4314 object: &serde_json::Map<String, Value>,
4315 field: &str,
4316 allowed: &[&str],
4317) -> Result<(), LingerError> {
4318 if let Some(value) = object.get(field) {
4319 let Some(value) = value.as_str() else {
4320 return Err(LingerError::invalid_config(format!(
4321 "image_generation {field} must be a string"
4322 )));
4323 };
4324 validate_optional_string(&format!("image_generation {field}"), Some(value))?;
4325 if !allowed.contains(&value) {
4326 return Err(LingerError::invalid_config(format!(
4327 "image_generation {field} has an unsupported value"
4328 )));
4329 }
4330 }
4331 Ok(())
4332}
4333
4334fn validate_image_generation_mask(input_image_mask: &Value) -> Result<(), LingerError> {
4335 if input_image_mask.is_null() {
4336 return Err(LingerError::invalid_config(
4337 "image_generation input_image_mask must not be null",
4338 ));
4339 }
4340 let mask = input_image_mask.as_object().ok_or_else(|| {
4341 LingerError::invalid_config("image_generation input_image_mask must be a JSON object")
4342 })?;
4343 let image_url = mask.get("image_url").and_then(Value::as_str);
4344 let file_id = mask.get("file_id").and_then(Value::as_str);
4345 match (image_url, file_id) {
4346 (Some(image_url), None) => validate_optional_string(
4347 "image_generation input_image_mask.image_url",
4348 Some(image_url),
4349 ),
4350 (None, Some(file_id)) => {
4351 validate_optional_string("image_generation input_image_mask.file_id", Some(file_id))
4352 }
4353 (None, None) => Err(LingerError::invalid_config(
4354 "image_generation input_image_mask must include image_url or file_id",
4355 )),
4356 (Some(_), Some(_)) => Err(LingerError::invalid_config(
4357 "image_generation input_image_mask must not include both image_url and file_id",
4358 )),
4359 }
4360}
4361
4362fn validate_mcp_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
4363 let server_label = object
4364 .get("server_label")
4365 .and_then(Value::as_str)
4366 .unwrap_or_default();
4367 validate_optional_string("mcp server_label", Some(server_label))?;
4368
4369 let server_url = validate_mcp_optional_string(object, "server_url")?;
4370 let connector_id = validate_mcp_optional_string(object, "connector_id")?;
4371 match (server_url, connector_id) {
4372 (Some(_), Some(_)) => {
4373 return Err(LingerError::invalid_config(
4374 "mcp tools must include only one of server_url or connector_id",
4375 ));
4376 }
4377 (None, None) => {
4378 return Err(LingerError::invalid_config(
4379 "mcp tools must include server_url or connector_id",
4380 ));
4381 }
4382 (Some(_), None) => {}
4383 (None, Some(connector_id)) => validate_mcp_connector_id(connector_id)?,
4384 }
4385
4386 validate_mcp_optional_string(object, "authorization")?;
4387 validate_mcp_optional_string(object, "server_description")?;
4388
4389 if let Some(headers) = object.get("headers") {
4390 validate_mcp_headers(headers)?;
4391 }
4392 if let Some(allowed_tools) = object.get("allowed_tools") {
4393 validate_mcp_allowed_tools(allowed_tools)?;
4394 }
4395 if let Some(require_approval) = object.get("require_approval") {
4396 validate_mcp_require_approval(require_approval)?;
4397 }
4398 if let Some(defer_loading) = object.get("defer_loading") {
4399 if !defer_loading.is_boolean() {
4400 return Err(LingerError::invalid_config(
4401 "mcp defer_loading must be a boolean",
4402 ));
4403 }
4404 }
4405
4406 Ok(())
4407}
4408
4409fn validate_mcp_optional_string<'a>(
4410 object: &'a serde_json::Map<String, Value>,
4411 field: &str,
4412) -> Result<Option<&'a str>, LingerError> {
4413 let Some(value) = object.get(field) else {
4414 return Ok(None);
4415 };
4416 let Some(value) = value.as_str() else {
4417 return Err(LingerError::invalid_config(format!(
4418 "mcp {field} must be a string"
4419 )));
4420 };
4421 validate_optional_string(&format!("mcp {field}"), Some(value))?;
4422 Ok(Some(value))
4423}
4424
4425fn validate_mcp_connector_id(connector_id: &str) -> Result<(), LingerError> {
4426 if matches!(
4427 connector_id,
4428 "connector_dropbox"
4429 | "connector_gmail"
4430 | "connector_googlecalendar"
4431 | "connector_googledrive"
4432 | "connector_microsoftteams"
4433 | "connector_outlookcalendar"
4434 | "connector_outlookemail"
4435 | "connector_sharepoint"
4436 ) {
4437 Ok(())
4438 } else {
4439 Err(LingerError::invalid_config(
4440 "mcp connector_id has an unsupported value",
4441 ))
4442 }
4443}
4444
4445fn validate_mcp_headers(headers: &Value) -> Result<(), LingerError> {
4446 if headers.is_null() {
4447 return Err(LingerError::invalid_config("mcp headers must not be null"));
4448 }
4449 let headers = headers
4450 .as_object()
4451 .ok_or_else(|| LingerError::invalid_config("mcp headers must be a JSON object"))?;
4452 for (name, value) in headers {
4453 if name.trim().is_empty() {
4454 return Err(LingerError::invalid_config(
4455 "mcp header names must not be empty",
4456 ));
4457 }
4458 if !value.is_string() {
4459 return Err(LingerError::invalid_config(
4460 "mcp header values must be strings",
4461 ));
4462 }
4463 }
4464 Ok(())
4465}
4466
4467fn validate_mcp_allowed_tools(allowed_tools: &Value) -> Result<(), LingerError> {
4468 if allowed_tools.is_null() {
4469 return Err(LingerError::invalid_config(
4470 "mcp allowed_tools must not be null",
4471 ));
4472 }
4473 if let Some(tool_names) = allowed_tools.as_array() {
4474 return validate_mcp_tool_names("mcp allowed_tools", tool_names);
4475 }
4476 validate_mcp_tool_filter("mcp allowed_tools", allowed_tools)
4477}
4478
4479fn validate_mcp_require_approval(require_approval: &Value) -> Result<(), LingerError> {
4480 if require_approval.is_null() {
4481 return Err(LingerError::invalid_config(
4482 "mcp require_approval must not be null",
4483 ));
4484 }
4485 if let Some(mode) = require_approval.as_str() {
4486 if matches!(mode, "always" | "never") {
4487 return Ok(());
4488 }
4489 return Err(LingerError::invalid_config(
4490 "mcp require_approval must be always, never, or a filter object",
4491 ));
4492 }
4493 let approval = require_approval.as_object().ok_or_else(|| {
4494 LingerError::invalid_config("mcp require_approval must be a string or object")
4495 })?;
4496 for field in ["always", "never"] {
4497 if let Some(filter) = approval.get(field) {
4498 validate_mcp_tool_filter(&format!("mcp require_approval.{field}"), filter)?;
4499 }
4500 }
4501 for field in approval.keys() {
4502 if !matches!(field.as_str(), "always" | "never") {
4503 return Err(LingerError::invalid_config(
4504 "mcp require_approval only supports always and never filters",
4505 ));
4506 }
4507 }
4508 Ok(())
4509}
4510
4511fn validate_mcp_tool_filter(name: &str, filter: &Value) -> Result<(), LingerError> {
4512 if filter.is_null() {
4513 return Err(LingerError::invalid_config(format!(
4514 "{name} must not be null"
4515 )));
4516 }
4517 let filter = filter
4518 .as_object()
4519 .ok_or_else(|| LingerError::invalid_config(format!("{name} must be a JSON object")))?;
4520 if let Some(tool_names) = filter.get("tool_names") {
4521 let tool_names = tool_names.as_array().ok_or_else(|| {
4522 LingerError::invalid_config(format!("{name}.tool_names must be an array"))
4523 })?;
4524 validate_mcp_tool_names(&format!("{name}.tool_names"), tool_names)?;
4525 }
4526 if let Some(read_only) = filter.get("read_only") {
4527 if !read_only.is_boolean() {
4528 return Err(LingerError::invalid_config(format!(
4529 "{name}.read_only must be a boolean"
4530 )));
4531 }
4532 }
4533 for field in filter.keys() {
4534 if !matches!(field.as_str(), "tool_names" | "read_only") {
4535 return Err(LingerError::invalid_config(format!(
4536 "{name} only supports tool_names and read_only"
4537 )));
4538 }
4539 }
4540 Ok(())
4541}
4542
4543fn validate_mcp_tool_names(name: &str, tool_names: &[Value]) -> Result<(), LingerError> {
4544 for tool_name in tool_names {
4545 let Some(tool_name) = tool_name.as_str() else {
4546 return Err(LingerError::invalid_config(format!(
4547 "{name} must contain strings"
4548 )));
4549 };
4550 validate_optional_string(name, Some(tool_name))?;
4551 }
4552 Ok(())
4553}
4554
4555fn validate_web_search_tool(object: &serde_json::Map<String, Value>) -> Result<(), LingerError> {
4556 for field in ["filters", "user_location"] {
4557 if let Some(value) = object.get(field) {
4558 if value.is_null() {
4559 return Err(LingerError::invalid_config(format!(
4560 "web_search {field} must not be null"
4561 )));
4562 }
4563 if !value.is_object() {
4564 return Err(LingerError::invalid_config(format!(
4565 "web_search {field} must be a JSON object"
4566 )));
4567 }
4568 }
4569 }
4570 if let Some(content_types) = object.get("search_content_types") {
4571 let content_types = content_types.as_array().ok_or_else(|| {
4572 LingerError::invalid_config("web_search search_content_types must be an array")
4573 })?;
4574 for content_type in content_types {
4575 let Some(content_type) = content_type.as_str() else {
4576 return Err(LingerError::invalid_config(
4577 "web_search search_content_types must contain strings",
4578 ));
4579 };
4580 validate_optional_string("web_search search_content_types", Some(content_type))?;
4581 }
4582 }
4583 Ok(())
4584}
4585
4586fn validate_tool_choice(tool_choice: &ResponseToolChoice) -> Result<(), LingerError> {
4587 if let ResponseToolChoice::AllowedTools { tools, .. } = tool_choice {
4588 validate_json_items("tool_choice.tools", tools)?;
4589 }
4590 Ok(())
4591}
4592
4593fn validate_prompt(prompt: &ResponsePrompt) -> Result<(), LingerError> {
4594 validate_optional_string("prompt.id", Some(&prompt.id))?;
4595 validate_optional_string("prompt.version", prompt.version.as_deref())?;
4596 for (name, value) in &prompt.variables {
4597 if name.trim().is_empty() {
4598 return Err(LingerError::invalid_config(
4599 "prompt variable names must not be empty",
4600 ));
4601 }
4602 if value.is_null() {
4603 return Err(LingerError::invalid_config(
4604 "prompt variable values must not be null",
4605 ));
4606 }
4607 }
4608 Ok(())
4609}
4610
4611fn validate_text_config(text: &ResponseTextConfig) -> Result<(), LingerError> {
4612 if let Some(ResponseTextFormat::JsonSchema { name, schema, .. }) = &text.format {
4613 validate_optional_string("text.format.name", Some(name))?;
4614 if name.chars().count() > 64 {
4615 return Err(LingerError::invalid_config(
4616 "text.format.name must be at most 64 characters",
4617 ));
4618 }
4619 if !name
4620 .chars()
4621 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
4622 {
4623 return Err(LingerError::invalid_config(
4624 "text.format.name must contain only ASCII letters, digits, underscores, or dashes",
4625 ));
4626 }
4627 if schema.is_null() {
4628 return Err(LingerError::invalid_config(
4629 "text.format.schema must not be null",
4630 ));
4631 }
4632 }
4633 Ok(())
4634}
4635
4636fn validate_context_management(
4637 context_management: &[ResponseContextManagement],
4638) -> Result<(), LingerError> {
4639 if context_management.is_empty() {
4640 return Err(LingerError::invalid_config(
4641 "context_management must contain at least one entry",
4642 ));
4643 }
4644 if context_management.iter().any(|entry| {
4645 entry
4646 .compact_threshold
4647 .is_some_and(|compact_threshold| compact_threshold < 1000)
4648 }) {
4649 return Err(LingerError::invalid_config(
4650 "context_management compact_threshold must be at least 1000",
4651 ));
4652 }
4653 Ok(())
4654}
4655
4656fn validate_moderation(moderation: &ResponseModeration) -> Result<(), LingerError> {
4657 if moderation.model.trim().is_empty() {
4658 return Err(LingerError::invalid_config(
4659 "moderation model must not be empty",
4660 ));
4661 }
4662 Ok(())
4663}
4664
4665fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
4666 for (key, value) in extra {
4667 if key.trim().is_empty() {
4668 return Err(LingerError::invalid_config(
4669 "extra field names must not be empty",
4670 ));
4671 }
4672 if value.is_null() {
4673 return Err(LingerError::invalid_config(format!(
4674 "extra field {key} must not be null"
4675 )));
4676 }
4677 }
4678 Ok(())
4679}