Skip to main content

gproxy_protocol/wire/openai/responses/
websocket.rs

1//! Responses WebSocket messages, separate from the HTTP upgrade handshake.
2//!
3//! Sources: the Responses WebSocket events reference and WebSocket mode guide.
4//! Lane/prefill controls stay separate from HTTP DTOs. Steering has its own
5//! acknowledgements, pending inputs, and automatic continuation lifecycle.
6//! The transport/host owns the duplex connection and continuation orchestration.
7
8use super::{generate::GenerateContentRequestBody, stream::StreamEvent};
9
10#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
11#[serde(tag = "type")]
12#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
13#[derive(gproxy_protocol_macros::DeclaredFields)]
14// Preserve the established by-value response.create constructor.
15#[allow(clippy::large_enum_variant)]
16pub enum ClientEvent {
17    #[serde(rename = "response.inject")]
18    ResponseInject(super::multi_agent::InjectRequest),
19    #[serde(rename = "response.create")]
20    ResponseCreate(GenerateContentRequestBody),
21    #[serde(rename = "response.steer")]
22    ResponseSteer(super::steering::SteerRequest),
23    #[serde(rename = "response.interrupt")]
24    ResponseInterrupt(InterruptRequest),
25}
26
27/// Generation and steering have distinct lifecycles on the same connection.
28#[derive(
29    Debug,
30    Clone,
31    PartialEq,
32    serde::Serialize,
33    serde::Deserialize,
34    gproxy_protocol_macros::DeclaredFields,
35)]
36#[serde(untagged)]
37#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
38// Match the by-value event DTOs used by native generation streams.
39#[allow(clippy::large_enum_variant)]
40pub enum ServerEvent {
41    Interrupt(InterruptEvent),
42    Injection(super::multi_agent::InjectionEvent),
43    Steering(super::steering::SteeringEvent),
44    Response(StreamEvent),
45}
46
47#[derive(
48    Debug,
49    Clone,
50    PartialEq,
51    Eq,
52    serde::Serialize,
53    serde::Deserialize,
54    gproxy_protocol_macros::DeclaredFields,
55)]
56pub struct InterruptRequest {
57    pub response_id: String,
58    pub mode: InterruptMode,
59}
60
61#[derive(
62    Debug,
63    Clone,
64    Copy,
65    PartialEq,
66    Eq,
67    serde::Serialize,
68    serde::Deserialize,
69    gproxy_protocol_macros::DeclaredFields,
70)]
71#[serde(rename_all = "snake_case")]
72pub enum InterruptMode {
73    DiscardPartialItems,
74}
75
76#[derive(
77    Debug,
78    Clone,
79    PartialEq,
80    Eq,
81    serde::Serialize,
82    serde::Deserialize,
83    gproxy_protocol_macros::DeclaredFields,
84)]
85#[serde(tag = "type")]
86pub enum InterruptEvent {
87    #[serde(rename = "response.interrupt.accepted")]
88    Accepted {
89        response_id: String,
90        sequence_number: i64,
91    },
92    #[serde(rename = "response.output_item.interrupted")]
93    OutputItem {
94        response_id: String,
95        item_id: String,
96        output_index: i64,
97        sequence_number: i64,
98    },
99}
100pub type HandshakeRequest = crate::WireRequest<()>;
101pub type HandshakeResponse = crate::WireResponse<()>;
102
103/// WS-only controls from the current official WebSocket mode guide.
104/// They never enter the HTTP generation request body.
105#[derive(
106    Debug,
107    Clone,
108    PartialEq,
109    serde::Serialize,
110    serde::Deserialize,
111    gproxy_protocol_macros::DeclaredFields,
112)]
113pub struct RequestMessage {
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    pub stream_id: Option<String>,
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub generate: Option<bool>,
118    #[serde(flatten)]
119    pub event: ClientEvent,
120}
121#[derive(
122    Debug,
123    Clone,
124    PartialEq,
125    serde::Serialize,
126    serde::Deserialize,
127    gproxy_protocol_macros::DeclaredFields,
128)]
129pub struct ServerMessage {
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub stream_id: Option<String>,
132    #[serde(flatten)]
133    pub event: ServerEvent,
134}
135#[derive(
136    Debug,
137    Clone,
138    PartialEq,
139    serde::Serialize,
140    serde::Deserialize,
141    gproxy_protocol_macros::DeclaredFields,
142)]
143pub struct ErrorMessage {
144    #[serde(rename = "type")]
145    pub type_: ErrorMessageType,
146    pub status: u16,
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub stream_id: Option<String>,
149    pub error: ErrorDetail,
150}
151#[derive(
152    Debug,
153    Clone,
154    Copy,
155    PartialEq,
156    serde::Serialize,
157    serde::Deserialize,
158    gproxy_protocol_macros::DeclaredFields,
159)]
160pub enum ErrorMessageType {
161    #[serde(rename = "error")]
162    Error,
163}
164#[derive(
165    Debug,
166    Clone,
167    PartialEq,
168    serde::Serialize,
169    serde::Deserialize,
170    gproxy_protocol_macros::DeclaredFields,
171)]
172pub struct ErrorDetail {
173    #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
174    pub type_: Option<String>,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub code: Option<String>,
177    pub message: String,
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub param: Option<String>,
180}