Skip to main content

gproxy_protocol/protocol/openai/realtime/
items.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize, de};
4use serde_json::Value;
5
6use super::super::common::*;
7use super::realtime_string_enum;
8
9/// Conversation item as it appears in realtime events. Unknown item types
10/// (e.g. future MCP items) round-trip through [`UnknownRealtimeItem`].
11#[derive(Debug, Clone, PartialEq, Serialize)]
12#[serde(untagged)]
13#[non_exhaustive]
14pub enum RealtimeItem {
15    Known(KnownRealtimeItem),
16    Unknown(UnknownRealtimeItem),
17}
18
19impl<'de> Deserialize<'de> for RealtimeItem {
20    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
21        let value = Value::deserialize(deserializer)?;
22        if let Ok(item) = serde_json::from_value(value.clone()) {
23            return Ok(Self::Known(item));
24        }
25        serde_json::from_value(value)
26            .map(Self::Unknown)
27            .map_err(de::Error::custom)
28    }
29}
30
31realtime_string_enum!(RealtimeRole, RealtimeRoleKnown {
32    User => "user",
33    Assistant => "assistant",
34    System => "system",
35});
36
37realtime_string_enum!(RealtimeItemStatus, RealtimeItemStatusKnown {
38    InProgress => "in_progress",
39    Completed => "completed",
40    Incomplete => "incomplete",
41});
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44#[serde(tag = "type")]
45#[non_exhaustive]
46pub enum KnownRealtimeItem {
47    #[serde(rename = "message")]
48    Message {
49        #[serde(skip_serializing_if = "Option::is_none")]
50        id: Option<String>,
51        role: RealtimeRole,
52        content: Vec<RealtimeContentPart>,
53        #[serde(skip_serializing_if = "Option::is_none")]
54        status: Option<RealtimeItemStatus>,
55        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
56        extra: Extra,
57    },
58    #[serde(rename = "function_call")]
59    FunctionCall {
60        #[serde(skip_serializing_if = "Option::is_none")]
61        id: Option<String>,
62        #[serde(skip_serializing_if = "Option::is_none")]
63        call_id: Option<String>,
64        #[serde(skip_serializing_if = "Option::is_none")]
65        name: Option<String>,
66        #[serde(skip_serializing_if = "Option::is_none")]
67        arguments: Option<String>,
68        #[serde(skip_serializing_if = "Option::is_none")]
69        status: Option<RealtimeItemStatus>,
70        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
71        extra: Extra,
72    },
73    #[serde(rename = "function_call_output")]
74    FunctionCallOutput {
75        #[serde(skip_serializing_if = "Option::is_none")]
76        id: Option<String>,
77        call_id: String,
78        output: String,
79        #[serde(skip_serializing_if = "Option::is_none")]
80        status: Option<RealtimeItemStatus>,
81        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
82        extra: Extra,
83    },
84}
85
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
87#[non_exhaustive]
88pub struct UnknownRealtimeItem {
89    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
90    pub type_: Option<String>,
91    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
92    pub extra: Extra,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96#[serde(tag = "type")]
97#[non_exhaustive]
98pub enum RealtimeContentPart {
99    #[serde(rename = "input_text")]
100    InputText {
101        #[serde(skip_serializing_if = "Option::is_none")]
102        text: Option<String>,
103        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
104        extra: Extra,
105    },
106    #[serde(rename = "input_audio")]
107    InputAudio {
108        #[serde(skip_serializing_if = "Option::is_none")]
109        audio: Option<String>,
110        #[serde(skip_serializing_if = "Option::is_none")]
111        transcript: Option<String>,
112        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
113        extra: Extra,
114    },
115    #[serde(rename = "text")]
116    Text {
117        #[serde(skip_serializing_if = "Option::is_none")]
118        text: Option<String>,
119        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
120        extra: Extra,
121    },
122    #[serde(rename = "audio")]
123    Audio {
124        #[serde(skip_serializing_if = "Option::is_none")]
125        audio: Option<String>,
126        #[serde(skip_serializing_if = "Option::is_none")]
127        transcript: Option<String>,
128        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
129        extra: Extra,
130    },
131    #[serde(rename = "output_text")]
132    OutputText {
133        #[serde(skip_serializing_if = "Option::is_none")]
134        text: Option<String>,
135        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
136        extra: Extra,
137    },
138    #[serde(rename = "output_audio")]
139    OutputAudio {
140        #[serde(skip_serializing_if = "Option::is_none")]
141        audio: Option<String>,
142        #[serde(skip_serializing_if = "Option::is_none")]
143        transcript: Option<String>,
144        #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
145        extra: Extra,
146    },
147}