1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use validator;
6
7pub fn default_unknown_model() -> String {
14 super::UNKNOWN_MODEL_ID.to_string()
15}
16
17pub fn default_true() -> bool {
19 true
20}
21
22pub fn deserialize_null_as_false<'de, D>(deserializer: D) -> Result<bool, D::Error>
27where
28 D: serde::Deserializer<'de>,
29{
30 Option::<bool>::deserialize(deserializer).map(|opt| opt.unwrap_or(false))
31}
32
33pub trait GenerationRequest: Send + Sync {
41 fn is_stream(&self) -> bool;
43
44 fn get_model(&self) -> Option<&str>;
46
47 fn extract_text_for_routing(&self) -> String;
49}
50
51#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
57#[serde(untagged)]
58pub enum StringOrArray {
59 String(String),
60 Array(Vec<String>),
61}
62
63impl StringOrArray {
64 pub fn len(&self) -> usize {
66 match self {
67 StringOrArray::String(_) => 1,
68 StringOrArray::Array(arr) => arr.len(),
69 }
70 }
71
72 pub fn is_empty(&self) -> bool {
74 match self {
75 StringOrArray::String(s) => s.is_empty(),
76 StringOrArray::Array(arr) => arr.is_empty(),
77 }
78 }
79
80 pub fn to_vec(&self) -> Vec<String> {
82 match self {
83 StringOrArray::String(s) => vec![s.clone()],
84 StringOrArray::Array(arr) => arr.clone(),
85 }
86 }
87
88 pub fn iter(&self) -> StringOrArrayIter<'_> {
91 StringOrArrayIter {
92 inner: self,
93 index: 0,
94 }
95 }
96
97 pub fn first(&self) -> Option<&str> {
99 match self {
100 StringOrArray::String(s) => {
101 if s.is_empty() {
102 None
103 } else {
104 Some(s)
105 }
106 }
107 StringOrArray::Array(arr) => arr.first().map(|s| s.as_str()),
108 }
109 }
110}
111
112pub struct StringOrArrayIter<'a> {
114 inner: &'a StringOrArray,
115 index: usize,
116}
117
118impl<'a> Iterator for StringOrArrayIter<'a> {
119 type Item = &'a str;
120
121 fn next(&mut self) -> Option<Self::Item> {
122 match self.inner {
123 StringOrArray::String(s) => {
124 if self.index == 0 {
125 self.index = 1;
126 Some(s.as_str())
127 } else {
128 None
129 }
130 }
131 StringOrArray::Array(arr) => {
132 if self.index < arr.len() {
133 let item = &arr[self.index];
134 self.index += 1;
135 Some(item.as_str())
136 } else {
137 None
138 }
139 }
140 }
141 }
142
143 fn size_hint(&self) -> (usize, Option<usize>) {
144 let remaining = match self.inner {
145 StringOrArray::String(_) => 1 - self.index,
146 StringOrArray::Array(arr) => arr.len() - self.index,
147 };
148 (remaining, Some(remaining))
149 }
150}
151
152impl<'a> ExactSizeIterator for StringOrArrayIter<'a> {}
153
154pub fn validate_stop(stop: &StringOrArray) -> Result<(), validator::ValidationError> {
157 match stop {
158 StringOrArray::String(s) => {
159 if s.is_empty() {
160 return Err(validator::ValidationError::new(
161 "stop sequences cannot be empty",
162 ));
163 }
164 }
165 StringOrArray::Array(arr) => {
166 if arr.len() > 4 {
167 return Err(validator::ValidationError::new(
168 "maximum 4 stop sequences allowed",
169 ));
170 }
171 for s in arr {
172 if s.is_empty() {
173 return Err(validator::ValidationError::new(
174 "stop sequences cannot be empty",
175 ));
176 }
177 }
178 }
179 }
180 Ok(())
181}
182
183#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, schemars::JsonSchema)]
188#[serde(tag = "type")]
189pub enum ContentPart {
190 #[serde(rename = "text")]
191 Text { text: String },
192 #[serde(rename = "image_url")]
193 ImageUrl { image_url: ImageUrl },
194 #[serde(rename = "video_url")]
195 VideoUrl { video_url: VideoUrl },
196}
197
198#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, schemars::JsonSchema)]
199pub struct ImageUrl {
200 pub url: String,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub detail: Option<String>, }
204
205#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, schemars::JsonSchema)]
206pub struct VideoUrl {
207 pub url: String,
208}
209
210#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
215#[serde(tag = "type")]
216pub enum ResponseFormat {
217 #[serde(rename = "text")]
218 Text,
219 #[serde(rename = "json_object")]
220 JsonObject,
221 #[serde(rename = "json_schema")]
222 JsonSchema { json_schema: JsonSchemaFormat },
223}
224
225#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
226pub struct JsonSchemaFormat {
227 pub name: String,
228 pub schema: Value,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub strict: Option<bool>,
231}
232
233#[derive(Debug, Clone, Default, Deserialize, Serialize, schemars::JsonSchema)]
238pub struct StreamOptions {
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub include_usage: Option<bool>,
242
243 #[serde(skip_serializing_if = "Option::is_none")]
246 pub continuous_usage_stats: Option<bool>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
251 pub include_obfuscation: Option<bool>,
252}
253
254#[serde_with::skip_serializing_none]
255#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
256pub struct ToolCallDelta {
257 pub index: u32,
258 pub id: Option<String>,
259 #[serde(rename = "type")]
260 pub tool_type: Option<String>,
261 pub function: Option<FunctionCallDelta>,
262}
263
264#[serde_with::skip_serializing_none]
265#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
266pub struct FunctionCallDelta {
267 pub name: Option<String>,
268 pub arguments: Option<String>,
269}
270
271#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
277#[serde(rename_all = "snake_case")]
278pub enum ToolChoiceValue {
279 Auto,
280 Required,
281 None,
282}
283
284#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
286#[serde(untagged)]
287pub enum ToolChoice {
288 Value(ToolChoiceValue),
289 Function {
290 #[serde(rename = "type")]
291 tool_type: String, function: FunctionChoice,
293 },
294 AllowedTools {
295 #[serde(rename = "type")]
296 tool_type: String, mode: String, tools: Vec<ToolReference>,
299 },
300}
301
302impl Default for ToolChoice {
303 fn default() -> Self {
304 Self::Value(ToolChoiceValue::Auto)
305 }
306}
307
308impl ToolChoice {
309 pub fn serialize_to_string(tool_choice: Option<&ToolChoice>) -> String {
313 tool_choice
314 .map(|tc| serde_json::to_string(tc).unwrap_or_else(|_| "auto".to_string()))
315 .unwrap_or_else(|| "auto".to_string())
316 }
317}
318
319#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
321pub struct FunctionChoice {
322 pub name: String,
323}
324
325#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
330#[serde(tag = "type")]
331#[serde(rename_all = "snake_case")]
332pub enum ToolReference {
333 #[serde(rename = "function")]
335 Function { name: String },
336
337 #[serde(rename = "mcp")]
339 Mcp {
340 server_label: String,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 name: Option<String>,
343 },
344
345 #[serde(rename = "file_search")]
347 FileSearch,
348
349 #[serde(rename = "web_search_preview")]
351 WebSearchPreview,
352
353 #[serde(rename = "computer_use_preview")]
355 ComputerUsePreview,
356
357 #[serde(rename = "code_interpreter")]
359 CodeInterpreter,
360
361 #[serde(rename = "image_generation")]
363 ImageGeneration,
364}
365
366impl ToolReference {
367 pub fn identifier(&self) -> String {
369 match self {
370 ToolReference::Function { name } => format!("function:{name}"),
371 ToolReference::Mcp { server_label, name } => {
372 if let Some(n) = name {
373 format!("mcp:{server_label}:{n}")
374 } else {
375 format!("mcp:{server_label}")
376 }
377 }
378 ToolReference::FileSearch => "file_search".to_string(),
379 ToolReference::WebSearchPreview => "web_search_preview".to_string(),
380 ToolReference::ComputerUsePreview => "computer_use_preview".to_string(),
381 ToolReference::CodeInterpreter => "code_interpreter".to_string(),
382 ToolReference::ImageGeneration => "image_generation".to_string(),
383 }
384 }
385
386 pub fn function_name(&self) -> Option<&str> {
388 match self {
389 ToolReference::Function { name } => Some(name.as_str()),
390 _ => None,
391 }
392 }
393}
394
395#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
396pub struct Tool {
397 #[serde(rename = "type")]
398 pub tool_type: String, pub function: Function,
400}
401
402#[serde_with::skip_serializing_none]
403#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
404pub struct Function {
405 pub name: String,
406 pub description: Option<String>,
407 pub parameters: Value, pub strict: Option<bool>,
410}
411
412#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
413pub struct ToolCall {
414 pub id: String,
415 #[serde(rename = "type")]
416 pub tool_type: String, pub function: FunctionCallResponse,
418}
419
420#[derive(Debug, Clone)]
423pub enum FunctionCall {
424 None,
425 Auto,
426 Function { name: String },
427}
428
429impl Serialize for FunctionCall {
430 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
431 match self {
432 FunctionCall::None => serializer.serialize_str("none"),
433 FunctionCall::Auto => serializer.serialize_str("auto"),
434 FunctionCall::Function { name } => {
435 use serde::ser::SerializeMap;
436 let mut map = serializer.serialize_map(Some(1))?;
437 map.serialize_entry("name", name)?;
438 map.end()
439 }
440 }
441 }
442}
443
444impl<'de> Deserialize<'de> for FunctionCall {
445 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
446 let value = Value::deserialize(deserializer)?;
447 match &value {
448 Value::String(s) => match s.as_str() {
449 "none" => Ok(FunctionCall::None),
450 "auto" => Ok(FunctionCall::Auto),
451 other => Err(serde::de::Error::custom(format!(
452 "unknown function_call value: \"{other}\""
453 ))),
454 },
455 Value::Object(map) => {
456 if let Some(Value::String(name)) = map.get("name") {
457 Ok(FunctionCall::Function { name: name.clone() })
458 } else {
459 Err(serde::de::Error::custom(
460 "function_call object must have a \"name\" string field",
461 ))
462 }
463 }
464 _ => Err(serde::de::Error::custom(
465 "function_call must be a string or object",
466 )),
467 }
468 }
469}
470
471impl schemars::JsonSchema for FunctionCall {
472 fn schema_name() -> String {
473 "FunctionCall".to_string()
474 }
475 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
476 use schemars::schema::*;
477 let string_schema = SchemaObject {
479 instance_type: Some(InstanceType::String.into()),
480 enum_values: Some(vec!["none".into(), "auto".into()]),
481 ..Default::default()
482 };
483 let object_schema = SchemaObject {
484 instance_type: Some(InstanceType::Object.into()),
485 object: Some(Box::new(ObjectValidation {
486 properties: {
487 let mut map = schemars::Map::new();
488 map.insert("name".to_string(), gen.subschema_for::<String>());
489 map
490 },
491 required: {
492 let mut set = std::collections::BTreeSet::new();
493 set.insert("name".to_string());
494 set
495 },
496 ..Default::default()
497 })),
498 ..Default::default()
499 };
500 SchemaObject {
501 subschemas: Some(Box::new(SubschemaValidation {
502 any_of: Some(vec![string_schema.into(), object_schema.into()]),
503 ..Default::default()
504 })),
505 ..Default::default()
506 }
507 .into()
508 }
509}
510
511#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
512pub struct FunctionCallResponse {
513 pub name: String,
514 #[serde(default)]
515 pub arguments: Option<String>, }
517
518#[serde_with::skip_serializing_none]
522#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
523pub struct Usage {
524 pub prompt_tokens: u32,
525 pub completion_tokens: u32,
526 pub total_tokens: u32,
527 pub prompt_tokens_details: Option<PromptTokenUsageInfo>,
528 pub completion_tokens_details: Option<CompletionTokensDetails>,
529}
530
531impl Usage {
532 pub fn from_counts(prompt_tokens: u32, completion_tokens: u32) -> Self {
534 Self {
535 prompt_tokens,
536 completion_tokens,
537 total_tokens: prompt_tokens + completion_tokens,
538 prompt_tokens_details: None,
539 completion_tokens_details: None,
540 }
541 }
542
543 pub fn with_cached_tokens(mut self, cached_tokens: u32) -> Self {
545 if cached_tokens > 0 {
546 self.prompt_tokens_details = Some(PromptTokenUsageInfo { cached_tokens });
547 }
548 self
549 }
550
551 pub fn with_reasoning_tokens(mut self, reasoning_tokens: u32) -> Self {
553 if reasoning_tokens > 0 {
554 self.completion_tokens_details = Some(CompletionTokensDetails {
555 reasoning_tokens: Some(reasoning_tokens),
556 accepted_prediction_tokens: None,
557 rejected_prediction_tokens: None,
558 });
559 }
560 self
561 }
562}
563
564#[serde_with::skip_serializing_none]
565#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
566pub struct CompletionTokensDetails {
567 pub reasoning_tokens: Option<u32>,
568 pub accepted_prediction_tokens: Option<u32>,
569 pub rejected_prediction_tokens: Option<u32>,
570}
571
572#[serde_with::skip_serializing_none]
574#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
575pub struct UsageInfo {
576 pub prompt_tokens: u32,
577 pub completion_tokens: u32,
578 pub total_tokens: u32,
579 pub reasoning_tokens: Option<u32>,
580 pub prompt_tokens_details: Option<PromptTokenUsageInfo>,
581}
582
583#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
584pub struct PromptTokenUsageInfo {
585 pub cached_tokens: u32,
586}
587
588#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
589pub struct LogProbs {
590 pub tokens: Vec<String>,
591 pub token_logprobs: Vec<Option<f32>>,
592 pub top_logprobs: Vec<Option<HashMap<String, f32>>>,
593 pub text_offset: Vec<u32>,
594}
595
596#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
597#[serde(untagged)]
598pub enum ChatLogProbs {
599 Detailed {
600 #[serde(skip_serializing_if = "Option::is_none")]
601 content: Option<Vec<ChatLogProbsContent>>,
602 },
603 Raw(Value),
604}
605
606#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
607pub struct ChatLogProbsContent {
608 pub token: String,
609 pub logprob: f32,
610 pub bytes: Option<Vec<u8>>,
611 pub top_logprobs: Vec<TopLogProb>,
612}
613
614#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
615pub struct TopLogProb {
616 pub token: String,
617 pub logprob: f32,
618 pub bytes: Option<Vec<u8>>,
619}
620
621#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
626pub struct ErrorResponse {
627 pub error: ErrorDetail,
628}
629
630#[serde_with::skip_serializing_none]
631#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
632pub struct ErrorDetail {
633 pub message: String,
634 #[serde(rename = "type")]
635 pub error_type: String,
636 pub param: Option<String>,
637 pub code: Option<String>,
638}
639
640#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
645#[serde(untagged)]
646pub enum InputIds {
647 Single(Vec<i32>),
648 Batch(Vec<Vec<i32>>),
649}
650
651#[derive(Debug, Clone, Deserialize, Serialize, schemars::JsonSchema)]
653#[serde(untagged)]
654pub enum LoRAPath {
655 Single(Option<String>),
656 Batch(Vec<Option<String>>),
657}
658
659#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
663pub struct Redacted(pub String);
664
665impl std::fmt::Debug for Redacted {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 f.write_str("[REDACTED]")
668 }
669}
670
671#[serde_with::skip_serializing_none]
677#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
678pub struct ResponsePrompt {
679 pub id: String,
680 pub variables: Option<HashMap<String, PromptVariable>>,
681 pub version: Option<String>,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
689#[serde(untagged)]
690pub enum PromptVariable {
691 String(String),
692 Typed(PromptVariableTyped),
693}
694
695#[serde_with::skip_serializing_none]
697#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
698#[serde(tag = "type")]
699#[expect(
700 clippy::enum_variant_names,
701 reason = "variant names match OpenAI API spec"
702)]
703pub enum PromptVariableTyped {
704 #[serde(rename = "input_text")]
705 ResponseInputText { text: String },
706 #[serde(rename = "input_image")]
707 ResponseInputImage {
708 detail: Option<Detail>,
709 file_id: Option<String>,
710 image_url: Option<String>,
711 },
712 #[serde(rename = "input_file")]
713 ResponseInputFile {
714 file_data: Option<String>,
715 file_id: Option<String>,
716 file_url: Option<String>,
717 filename: Option<String>,
718 },
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, Default, schemars::JsonSchema)]
725#[serde(rename_all = "snake_case")]
726pub enum Detail {
727 Low,
728 High,
729 #[default]
730 Auto,
731 Original,
732}
733
734#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
742pub enum PromptCacheRetention {
743 #[serde(rename = "in-memory")]
744 InMemory,
745 #[serde(rename = "24h")]
746 Duration24h,
747}
748
749#[serde_with::skip_serializing_none]
754#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
755pub struct ContextManagementEntry {
756 #[serde(rename = "type")]
757 pub r#type: ContextManagementType,
758 pub compact_threshold: Option<u32>,
759}
760
761#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
765#[serde(rename_all = "snake_case")]
766pub enum ContextManagementType {
767 Compaction,
768}
769
770#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
780#[serde(untagged)]
781pub enum ConversationRef {
782 Id(String),
783 Object { id: String },
784}
785
786impl ConversationRef {
787 pub fn as_id(&self) -> &str {
789 match self {
790 Self::Id(id) | Self::Object { id } => id.as_str(),
791 }
792 }
793
794 pub fn is_empty(&self) -> bool {
798 self.as_id().is_empty()
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use serde::Deserialize;
805 use serde_json::json;
806
807 use super::*;
808
809 #[derive(Deserialize)]
810 struct NullableBoolTest {
811 #[serde(default, deserialize_with = "deserialize_null_as_false")]
812 field: bool,
813 }
814
815 #[test]
816 fn test_deserialize_null_as_false() {
817 let cases = [
818 (json!({"field": true}), true),
819 (json!({"field": false}), false),
820 (json!({"field": null}), false),
821 (json!({}), false),
822 ];
823 for (input, expected) in cases {
824 let t: NullableBoolTest = serde_json::from_value(input).unwrap();
825 assert_eq!(t.field, expected);
826 }
827 }
828
829 #[test]
830 fn test_deserialize_null_as_false_rejects_non_bool() {
831 let result = serde_json::from_value::<NullableBoolTest>(json!({"field": "yes"}));
832 assert!(result.is_err());
833 }
834
835 #[test]
836 fn conversation_ref_deserializes_bare_string() {
837 let v = json!("conv_abc");
838 let r: ConversationRef = serde_json::from_value(v).expect("string form");
839 assert!(matches!(r, ConversationRef::Id(ref s) if s == "conv_abc"));
840 assert_eq!(r.as_id(), "conv_abc");
841 assert_eq!(serde_json::to_value(&r).unwrap(), json!("conv_abc"));
843 }
844
845 #[test]
846 fn conversation_ref_deserializes_object() {
847 let v = json!({"id": "conv_xyz"});
848 let r: ConversationRef = serde_json::from_value(v).expect("object form");
849 assert!(matches!(r, ConversationRef::Object { ref id } if id == "conv_xyz"));
850 assert_eq!(r.as_id(), "conv_xyz");
851 assert_eq!(serde_json::to_value(&r).unwrap(), json!({"id": "conv_xyz"}));
853 }
854
855 #[test]
856 fn conversation_ref_is_empty() {
857 assert!(ConversationRef::Id(String::new()).is_empty());
858 assert!(!ConversationRef::Id("conv_1".to_string()).is_empty());
859 assert!(ConversationRef::Object { id: String::new() }.is_empty());
860 }
861}