Skip to main content

gproxy_protocol/wire/openai/chat/
stream.rs

1//! Typed JSON records for Chat Completions streaming; `[DONE]` is SSE framing,
2//! not a JSON chunk. `StreamResponse` carries raw transport bytes separately.
3//!
4//! Verified against openai-python main on 2026-09-13:
5//! https://github.com/openai/openai-python/blob/main/src/openai/types/chat/chat_completion_chunk.py
6//! https://github.com/openai/openai-python/blob/main/src/openai/types/chat/chat_completion_token_logprob.py
7//! https://github.com/openai/openai-python/blob/main/src/openai/types/completion_usage.py
8//! The SDK chunk has function-call fragments (not non-streaming custom calls),
9//! five role values, moderation, and obfuscation. The chunk source last-change
10//! commit was f348ec87b934c98889102668913e0a3ae7fc303d. Referenced SDK token
11//! bytes are optional (unlike the local non-streaming document), and usage adds
12//! text/image details; these use dedicated chunk types here.
13//! Audio is not a declared delta
14//! field in this source; unknown extensions are retained in `rest`.
15
16use super::content::present_nullable;
17use super::request::GenerateContentRequest;
18use super::response::{FinishReason, ResponseServiceTier};
19use crate::connection::ByteStream;
20use crate::{Rest, WireResponse};
21use serde::{Deserialize, Serialize};
22
23pub type StreamRequest = GenerateContentRequest;
24pub type StreamResponse = WireResponse<ByteStream>;
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
27#[serde(rename_all = "snake_case")]
28#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
29#[derive(gproxy_protocol_macros::DeclaredFields)]
30pub struct ChatCompletionChunk {
31    #[serde(
32        default,
33        deserialize_with = "present_nullable",
34        skip_serializing_if = "Option::is_none"
35    )]
36    pub moderation: Option<Option<super::response::ModerationResponse>>,
37    pub id: String,
38    pub choices: Vec<StreamChoice>,
39    pub created: i64,
40    pub model: String,
41    pub object: ChunkObject,
42    #[serde(
43        default,
44        deserialize_with = "present_nullable",
45        skip_serializing_if = "Option::is_none"
46    )]
47    pub service_tier: Option<Option<ResponseServiceTier>>,
48    #[serde(
49        default,
50        deserialize_with = "present_nullable",
51        skip_serializing_if = "Option::is_none"
52    )]
53    pub system_fingerprint: Option<Option<String>>,
54    #[serde(
55        default,
56        deserialize_with = "present_nullable",
57        skip_serializing_if = "Option::is_none"
58    )]
59    pub usage: Option<Option<ChunkUsage>>,
60    #[serde(
61        default,
62        deserialize_with = "present_nullable",
63        skip_serializing_if = "Option::is_none"
64    )]
65    pub obfuscation: Option<Option<String>>,
66    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
67    pub rest: Rest,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
72#[derive(gproxy_protocol_macros::DeclaredFields)]
73pub enum ChunkObject {
74    #[serde(rename = "chat.completion.chunk")]
75    ChatCompletionChunk,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
79#[serde(rename_all = "snake_case")]
80#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
81#[derive(gproxy_protocol_macros::DeclaredFields)]
82pub struct StreamChoice {
83    pub index: i64,
84    pub delta: Delta,
85    #[serde(
86        default,
87        deserialize_with = "present_nullable",
88        skip_serializing_if = "Option::is_none"
89    )]
90    pub logprobs: Option<Option<ChunkLogprobs>>,
91    #[serde(
92        default,
93        deserialize_with = "present_nullable",
94        skip_serializing_if = "Option::is_none"
95    )]
96    pub finish_reason: Option<Option<FinishReason>>,
97    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
98    pub rest: Rest,
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
102#[serde(rename_all = "snake_case")]
103#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
104#[derive(gproxy_protocol_macros::DeclaredFields)]
105pub struct Delta {
106    #[serde(
107        default,
108        deserialize_with = "present_nullable",
109        skip_serializing_if = "Option::is_none"
110    )]
111    pub reasoning_details: Option<Option<Vec<super::reasoning::ReasoningDetail>>>,
112    /// Plain-text reasoning returned by compatible providers (for example DeepSeek).
113    #[serde(
114        default,
115        deserialize_with = "present_nullable",
116        skip_serializing_if = "Option::is_none"
117    )]
118    pub reasoning_content: Option<Option<String>>,
119    /// Alternative compatible-provider spelling. Kept separately on the wire.
120    #[serde(
121        default,
122        deserialize_with = "present_nullable",
123        skip_serializing_if = "Option::is_none"
124    )]
125    pub reasoning: Option<Option<String>>,
126    #[serde(
127        default,
128        deserialize_with = "present_nullable",
129        skip_serializing_if = "Option::is_none"
130    )]
131    pub role: Option<Option<DeltaRole>>,
132    #[serde(
133        default,
134        deserialize_with = "present_nullable",
135        skip_serializing_if = "Option::is_none"
136    )]
137    pub content: Option<Option<String>>,
138    #[serde(
139        default,
140        deserialize_with = "present_nullable",
141        skip_serializing_if = "Option::is_none"
142    )]
143    pub refusal: Option<Option<String>>,
144    #[serde(
145        default,
146        deserialize_with = "present_nullable",
147        skip_serializing_if = "Option::is_none"
148    )]
149    pub function_call: Option<Option<DeltaFunctionCall>>,
150    #[serde(
151        default,
152        deserialize_with = "present_nullable",
153        skip_serializing_if = "Option::is_none"
154    )]
155    pub tool_calls: Option<Option<Vec<DeltaToolCall>>>,
156    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
157    pub rest: Rest,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
161#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
162#[derive(gproxy_protocol_macros::DeclaredFields)]
163pub enum DeltaRole {
164    #[serde(rename = "developer")]
165    Developer,
166    #[serde(rename = "system")]
167    System,
168    #[serde(rename = "user")]
169    User,
170    #[serde(rename = "tool")]
171    Tool,
172    #[serde(rename = "assistant")]
173    Assistant,
174}
175
176#[derive(
177    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
178)]
179#[serde(rename_all = "snake_case")]
180#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
181#[derive(gproxy_protocol_macros::DeclaredFields)]
182pub struct DeltaFunctionCall {
183    #[serde(
184        default,
185        deserialize_with = "present_nullable",
186        skip_serializing_if = "Option::is_none"
187    )]
188    pub name: Option<Option<String>>,
189    #[serde(
190        default,
191        deserialize_with = "present_nullable",
192        skip_serializing_if = "Option::is_none"
193    )]
194    pub arguments: Option<Option<String>>,
195    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
196    pub rest: Rest,
197}
198
199#[derive(
200    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder,
201)]
202#[serde(rename_all = "snake_case")]
203#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
204#[derive(gproxy_protocol_macros::DeclaredFields)]
205pub struct DeltaToolCall {
206    pub index: i64,
207    #[serde(
208        default,
209        deserialize_with = "present_nullable",
210        skip_serializing_if = "Option::is_none"
211    )]
212    pub id: Option<Option<String>>,
213    #[serde(
214        default,
215        deserialize_with = "present_nullable",
216        skip_serializing_if = "Option::is_none",
217        rename = "type"
218    )]
219    pub type_: Option<Option<DeltaToolCallType>>,
220    #[serde(
221        default,
222        deserialize_with = "present_nullable",
223        skip_serializing_if = "Option::is_none"
224    )]
225    pub function: Option<Option<DeltaFunctionCall>>,
226    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
227    pub rest: Rest,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
231#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
232#[derive(gproxy_protocol_macros::DeclaredFields)]
233pub enum DeltaToolCallType {
234    #[serde(rename = "function")]
235    Function,
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
239#[serde(rename_all = "snake_case")]
240#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
241#[derive(gproxy_protocol_macros::DeclaredFields)]
242pub struct ChunkLogprobs {
243    #[serde(
244        default,
245        deserialize_with = "present_nullable",
246        skip_serializing_if = "Option::is_none"
247    )]
248    pub content: Option<Option<Vec<ChunkTokenLogprob>>>,
249    #[serde(
250        default,
251        deserialize_with = "present_nullable",
252        skip_serializing_if = "Option::is_none"
253    )]
254    pub refusal: Option<Option<Vec<ChunkTokenLogprob>>>,
255    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
256    pub rest: Rest,
257}
258
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
260#[serde(rename_all = "snake_case")]
261#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
262#[derive(gproxy_protocol_macros::DeclaredFields)]
263pub struct ChunkTokenLogprob {
264    pub token: String,
265    #[serde(
266        default,
267        deserialize_with = "present_nullable",
268        skip_serializing_if = "Option::is_none"
269    )]
270    pub bytes: Option<Option<Vec<i64>>>,
271    pub logprob: f64,
272    pub top_logprobs: Vec<ChunkTopLogprob>,
273    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
274    pub rest: Rest,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
278#[serde(rename_all = "snake_case")]
279#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
280#[derive(gproxy_protocol_macros::DeclaredFields)]
281pub struct ChunkTopLogprob {
282    pub token: String,
283    #[serde(
284        default,
285        deserialize_with = "present_nullable",
286        skip_serializing_if = "Option::is_none"
287    )]
288    pub bytes: Option<Option<Vec<i64>>>,
289    pub logprob: f64,
290    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
291    pub rest: Rest,
292}
293
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
295#[serde(rename_all = "snake_case")]
296#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
297#[derive(gproxy_protocol_macros::DeclaredFields)]
298pub struct ChunkUsage {
299    pub completion_tokens: i64,
300    pub prompt_tokens: i64,
301    pub total_tokens: i64,
302    #[serde(
303        default,
304        deserialize_with = "present_nullable",
305        skip_serializing_if = "Option::is_none"
306    )]
307    pub completion_tokens_details: Option<Option<ChunkCompletionTokensDetails>>,
308    #[serde(
309        default,
310        deserialize_with = "present_nullable",
311        skip_serializing_if = "Option::is_none"
312    )]
313    pub prompt_tokens_details: Option<Option<ChunkPromptTokensDetails>>,
314    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
315    pub rest: Rest,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
319#[serde(rename_all = "snake_case")]
320#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
321#[derive(gproxy_protocol_macros::DeclaredFields)]
322pub struct ChunkCompletionTokensDetails {
323    #[serde(
324        default,
325        deserialize_with = "present_nullable",
326        skip_serializing_if = "Option::is_none"
327    )]
328    pub accepted_prediction_tokens: Option<Option<i64>>,
329    #[serde(
330        default,
331        deserialize_with = "present_nullable",
332        skip_serializing_if = "Option::is_none"
333    )]
334    pub audio_tokens: Option<Option<i64>>,
335    #[serde(
336        default,
337        deserialize_with = "present_nullable",
338        skip_serializing_if = "Option::is_none"
339    )]
340    pub reasoning_tokens: Option<Option<i64>>,
341    #[serde(
342        default,
343        deserialize_with = "present_nullable",
344        skip_serializing_if = "Option::is_none"
345    )]
346    pub rejected_prediction_tokens: Option<Option<i64>>,
347    #[serde(
348        default,
349        deserialize_with = "present_nullable",
350        skip_serializing_if = "Option::is_none"
351    )]
352    pub text_tokens: Option<Option<i64>>,
353    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
354    pub rest: Rest,
355}
356
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
358#[serde(rename_all = "snake_case")]
359#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
360#[derive(gproxy_protocol_macros::DeclaredFields)]
361pub struct ChunkPromptTokensDetails {
362    #[serde(
363        default,
364        deserialize_with = "present_nullable",
365        skip_serializing_if = "Option::is_none"
366    )]
367    pub audio_tokens: Option<Option<i64>>,
368    #[serde(
369        default,
370        deserialize_with = "present_nullable",
371        skip_serializing_if = "Option::is_none"
372    )]
373    pub cache_write_tokens: Option<Option<i64>>,
374    #[serde(
375        default,
376        deserialize_with = "present_nullable",
377        skip_serializing_if = "Option::is_none"
378    )]
379    pub cached_tokens: Option<Option<i64>>,
380    #[serde(
381        default,
382        deserialize_with = "present_nullable",
383        skip_serializing_if = "Option::is_none"
384    )]
385    pub image_tokens: Option<Option<i64>>,
386    #[serde(
387        default,
388        deserialize_with = "present_nullable",
389        skip_serializing_if = "Option::is_none"
390    )]
391    pub text_tokens: Option<Option<i64>>,
392    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
393    pub rest: Rest,
394}