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