Skip to main content

gproxy_protocol/wire/openai/chat/
content.rs

1use crate::Rest;
2use serde::{Deserialize, Serialize};
3
4pub(crate) fn present_nullable<'de, D, T>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
5where
6    D: serde::Deserializer<'de>,
7    T: Deserialize<'de>,
8{
9    Option::<T>::deserialize(deserializer).map(Some)
10}
11
12pub(crate) fn required_nullable<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
13where
14    D: serde::Deserializer<'de>,
15    T: Deserialize<'de>,
16{
17    Option::<T>::deserialize(deserializer)
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21#[serde(untagged)]
22#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
23#[derive(gproxy_protocol_macros::DeclaredFields)]
24// Keep the public wire variants inline, as with the Claude content unions.
25#[allow(clippy::large_enum_variant)]
26pub enum ChatMessage {
27    Developer(DeveloperMessage),
28    System(SystemMessage),
29    User(UserMessage),
30    Assistant(AssistantMessage),
31    Tool(ToolMessage),
32    Function(FunctionMessage),
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
36#[serde(rename_all = "snake_case")]
37#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
38#[derive(gproxy_protocol_macros::DeclaredFields)]
39pub struct DeveloperMessage {
40    pub role: DeveloperRole,
41    pub content: TextContent,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub name: Option<String>,
44    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
45    pub rest: Rest,
46}
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
49#[derive(gproxy_protocol_macros::DeclaredFields)]
50pub enum DeveloperRole {
51    #[serde(rename = "developer")]
52    Developer,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
56#[serde(rename_all = "snake_case")]
57#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
58#[derive(gproxy_protocol_macros::DeclaredFields)]
59pub struct SystemMessage {
60    pub role: SystemRole,
61    pub content: TextContent,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub name: Option<String>,
64    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
65    pub rest: Rest,
66}
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
69#[derive(gproxy_protocol_macros::DeclaredFields)]
70pub enum SystemRole {
71    #[serde(rename = "system")]
72    System,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
76#[serde(rename_all = "snake_case")]
77#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
78#[derive(gproxy_protocol_macros::DeclaredFields)]
79pub struct UserMessage {
80    pub role: UserRole,
81    pub content: UserContent,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub name: Option<String>,
84    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
85    pub rest: Rest,
86}
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
89#[derive(gproxy_protocol_macros::DeclaredFields)]
90pub enum UserRole {
91    #[serde(rename = "user")]
92    User,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
96#[serde(rename_all = "snake_case")]
97#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
98#[derive(gproxy_protocol_macros::DeclaredFields)]
99pub struct AssistantMessage {
100    #[serde(
101        default,
102        deserialize_with = "present_nullable",
103        skip_serializing_if = "Option::is_none"
104    )]
105    pub reasoning_details: Option<Option<Vec<super::reasoning::ReasoningDetail>>>,
106    /// Plain-text reasoning returned by compatible providers (for example DeepSeek).
107    #[serde(
108        default,
109        deserialize_with = "present_nullable",
110        skip_serializing_if = "Option::is_none"
111    )]
112    pub reasoning_content: Option<Option<String>>,
113    /// Alternative compatible-provider spelling. Kept separately on the wire.
114    #[serde(
115        default,
116        deserialize_with = "present_nullable",
117        skip_serializing_if = "Option::is_none"
118    )]
119    pub reasoning: Option<Option<String>>,
120    pub role: AssistantRole,
121    #[serde(
122        default,
123        deserialize_with = "present_nullable",
124        skip_serializing_if = "Option::is_none"
125    )]
126    pub content: Option<Option<AssistantContent>>,
127    #[serde(
128        default,
129        deserialize_with = "present_nullable",
130        skip_serializing_if = "Option::is_none"
131    )]
132    pub audio: Option<Option<AssistantAudio>>,
133    #[serde(
134        default,
135        deserialize_with = "present_nullable",
136        skip_serializing_if = "Option::is_none"
137    )]
138    pub refusal: Option<Option<String>>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub name: Option<String>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub tool_calls: Option<Vec<MessageToolCall>>,
143    #[serde(
144        default,
145        deserialize_with = "present_nullable",
146        skip_serializing_if = "Option::is_none"
147    )]
148    pub function_call: Option<Option<FunctionCall>>,
149    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
150    pub rest: Rest,
151}
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
153#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
154#[derive(gproxy_protocol_macros::DeclaredFields)]
155pub enum AssistantRole {
156    #[serde(rename = "assistant")]
157    Assistant,
158}
159
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
161#[serde(rename_all = "snake_case")]
162#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
163#[derive(gproxy_protocol_macros::DeclaredFields)]
164pub struct ToolMessage {
165    pub role: ToolRole,
166    pub content: TextContent,
167    pub tool_call_id: String,
168    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
169    pub rest: Rest,
170}
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
172#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
173#[derive(gproxy_protocol_macros::DeclaredFields)]
174pub enum ToolRole {
175    #[serde(rename = "tool")]
176    Tool,
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
180#[serde(rename_all = "snake_case")]
181#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
182#[derive(gproxy_protocol_macros::DeclaredFields)]
183pub struct FunctionMessage {
184    pub role: FunctionRole,
185    #[wire(required)]
186    #[serde(deserialize_with = "required_nullable")]
187    pub content: Option<String>,
188    pub name: String,
189    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
190    pub rest: Rest,
191}
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
193#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
194#[derive(gproxy_protocol_macros::DeclaredFields)]
195pub enum FunctionRole {
196    #[serde(rename = "function")]
197    Function,
198}
199
200pub type TextContent = StringOrTextParts;
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
202#[serde(untagged)]
203#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
204#[derive(gproxy_protocol_macros::DeclaredFields)]
205pub enum StringOrTextParts {
206    Text(String),
207    Parts(Vec<TextPart>),
208}
209pub type UserContent = UserContentParts;
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211#[serde(untagged)]
212#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
213#[derive(gproxy_protocol_macros::DeclaredFields)]
214pub enum UserContentParts {
215    Text(String),
216    Parts(Vec<UserContentPart>),
217}
218pub type AssistantContent = AssistantContentParts;
219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220#[serde(untagged)]
221#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
222#[derive(gproxy_protocol_macros::DeclaredFields)]
223pub enum AssistantContentParts {
224    Text(String),
225    Parts(Vec<AssistantContentPart>),
226}
227
228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229#[serde(untagged)]
230#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
231#[derive(gproxy_protocol_macros::DeclaredFields)]
232pub enum UserContentPart {
233    #[serde(rename = "text")]
234    Text(TextPart),
235    #[serde(rename = "image_url")]
236    Image(ImagePart),
237    #[serde(rename = "input_audio")]
238    InputAudio(InputAudioPart),
239    #[serde(rename = "file")]
240    File(FilePart),
241}
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243#[serde(untagged)]
244#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
245#[derive(gproxy_protocol_macros::DeclaredFields)]
246pub enum AssistantContentPart {
247    #[serde(rename = "text")]
248    Text(TextPart),
249    #[serde(rename = "refusal")]
250    Refusal(RefusalPart),
251}
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
253#[serde(rename_all = "snake_case")]
254#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
255#[derive(gproxy_protocol_macros::DeclaredFields)]
256pub struct TextPart {
257    #[serde(rename = "type")]
258    pub type_: TextPartType,
259    pub text: String,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
262    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
263    pub rest: Rest,
264}
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
266#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
267#[derive(gproxy_protocol_macros::DeclaredFields)]
268pub enum TextPartType {
269    #[serde(rename = "text")]
270    Text,
271}
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
273#[serde(rename_all = "snake_case")]
274#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
275#[derive(gproxy_protocol_macros::DeclaredFields)]
276pub struct RefusalPart {
277    #[serde(rename = "type")]
278    pub type_: RefusalType,
279    pub refusal: String,
280    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
281    pub rest: Rest,
282}
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
284#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
285#[derive(gproxy_protocol_macros::DeclaredFields)]
286pub enum RefusalType {
287    #[serde(rename = "refusal")]
288    Refusal,
289}
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
291#[serde(rename_all = "snake_case")]
292#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
293#[derive(gproxy_protocol_macros::DeclaredFields)]
294pub struct ImagePart {
295    #[serde(rename = "type")]
296    pub type_: ImagePartType,
297    pub image_url: ImageUrl,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
300    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
301    pub rest: Rest,
302}
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
304#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
305#[derive(gproxy_protocol_macros::DeclaredFields)]
306pub enum ImagePartType {
307    #[serde(rename = "image_url")]
308    ImageUrl,
309}
310#[derive(
311    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
312)]
313#[serde(rename_all = "snake_case")]
314#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
315#[derive(gproxy_protocol_macros::DeclaredFields)]
316pub struct ImageUrl {
317    pub url: String,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub detail: Option<ImageDetail>,
320    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
321    pub rest: Rest,
322}
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
324#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
325#[derive(gproxy_protocol_macros::DeclaredFields)]
326pub enum ImageDetail {
327    #[serde(rename = "auto")]
328    Auto,
329    #[serde(rename = "low")]
330    Low,
331    #[serde(rename = "high")]
332    High,
333}
334#[derive(
335    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
336)]
337#[serde(rename_all = "snake_case")]
338#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
339#[derive(gproxy_protocol_macros::DeclaredFields)]
340pub struct InputAudioPart {
341    #[serde(rename = "type")]
342    pub type_: InputAudioPartType,
343    pub input_audio: InputAudio,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
346    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
347    pub rest: Rest,
348}
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
350#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
351#[derive(gproxy_protocol_macros::DeclaredFields)]
352pub enum InputAudioPartType {
353    #[serde(rename = "input_audio")]
354    InputAudio,
355}
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
357#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
358#[derive(gproxy_protocol_macros::DeclaredFields)]
359pub enum AudioFormat {
360    #[serde(rename = "wav")]
361    Wav,
362    #[serde(rename = "mp3")]
363    Mp3,
364}
365#[derive(
366    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
367)]
368#[serde(rename_all = "snake_case")]
369#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
370#[derive(gproxy_protocol_macros::DeclaredFields)]
371pub struct InputAudio {
372    pub data: String,
373    pub format: AudioFormat,
374    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
375    pub rest: Rest,
376}
377#[derive(
378    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
379)]
380#[serde(rename_all = "snake_case")]
381#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
382#[derive(gproxy_protocol_macros::DeclaredFields)]
383pub struct FilePart {
384    #[serde(rename = "type")]
385    pub type_: FilePartType,
386    pub file: FileRef,
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub prompt_cache_breakpoint: Option<PromptCacheBreakpoint>,
389    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
390    pub rest: Rest,
391}
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
393#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
394#[derive(gproxy_protocol_macros::DeclaredFields)]
395pub enum FilePartType {
396    #[serde(rename = "file")]
397    File,
398}
399#[derive(
400    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
401)]
402#[serde(rename_all = "snake_case")]
403#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
404#[derive(gproxy_protocol_macros::DeclaredFields)]
405pub struct FileRef {
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub file_data: Option<String>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub file_id: Option<String>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub filename: Option<String>,
412    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
413    pub rest: Rest,
414}
415#[derive(
416    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
417)]
418#[serde(rename_all = "snake_case")]
419#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
420#[derive(gproxy_protocol_macros::DeclaredFields)]
421pub struct PromptCacheBreakpoint {
422    pub mode: PromptCacheMode,
423    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
424    pub rest: Rest,
425}
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
427#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
428#[derive(gproxy_protocol_macros::DeclaredFields)]
429pub enum PromptCacheMode {
430    #[serde(rename = "explicit")]
431    Explicit,
432}
433
434#[derive(
435    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
436)]
437#[serde(rename_all = "snake_case")]
438#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
439#[derive(gproxy_protocol_macros::DeclaredFields)]
440pub struct AssistantAudio {
441    pub id: String,
442    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
443    pub rest: Rest,
444}
445#[derive(
446    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
447)]
448#[serde(rename_all = "snake_case")]
449#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
450#[derive(gproxy_protocol_macros::DeclaredFields)]
451pub struct FunctionCall {
452    pub arguments: String,
453    pub name: String,
454    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
455    pub rest: Rest,
456}
457#[derive(
458    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
459)]
460#[serde(rename_all = "snake_case")]
461#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
462#[derive(gproxy_protocol_macros::DeclaredFields)]
463pub struct ChatToolCall {
464    pub id: String,
465    pub function: FunctionCall,
466    #[serde(rename = "type")]
467    pub type_: ChatToolCallType,
468    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
469    pub rest: Rest,
470}
471#[derive(
472    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
473)]
474#[serde(rename_all = "snake_case")]
475#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
476#[derive(gproxy_protocol_macros::DeclaredFields)]
477pub struct CustomToolCall {
478    pub id: String,
479    pub custom: CustomCall,
480    #[serde(rename = "type")]
481    pub type_: CustomToolCallType,
482    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
483    pub rest: Rest,
484}
485#[derive(
486    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
487)]
488#[serde(rename_all = "snake_case")]
489#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
490#[derive(gproxy_protocol_macros::DeclaredFields)]
491pub struct CustomCall {
492    pub input: String,
493    pub name: String,
494    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
495    pub rest: Rest,
496}
497#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
498#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
499#[derive(gproxy_protocol_macros::DeclaredFields)]
500pub enum ChatToolCallType {
501    #[serde(rename = "function")]
502    Function,
503}
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
505#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
506#[derive(gproxy_protocol_macros::DeclaredFields)]
507pub enum CustomToolCallType {
508    #[serde(rename = "custom")]
509    Custom,
510}
511
512#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
513#[serde(untagged)]
514#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
515#[derive(gproxy_protocol_macros::DeclaredFields)]
516pub enum MessageToolCall {
517    Function(ChatToolCall),
518    Custom(CustomToolCall),
519}