use super::{ReasoningConfig, ResponsesInput, ResponsesTool, ResponsesToolChoice};
use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum ClientEvent {
ResponseCreate { response: ResponseCreateBody },
}
impl<'de> Deserialize<'de> for ClientEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let event_type = value
.get("type")
.and_then(|t| t.as_str())
.ok_or_else(|| serde::de::Error::missing_field("type"))?;
match event_type {
"response.create" => {
if let Some(response_obj) = value.get("response") {
let body: ResponseCreateBody = serde_json::from_value(response_obj.clone())
.map_err(|e| {
serde::de::Error::custom(format!("invalid response.create body: {}", e))
})?;
Ok(ClientEvent::ResponseCreate { response: body })
} else {
let body: ResponseCreateBody = serde_json::from_value(value).map_err(|e| {
serde::de::Error::custom(format!("invalid response.create body: {}", e))
})?;
Ok(ClientEvent::ResponseCreate { response: body })
}
}
other => Err(serde::de::Error::custom(format!(
"unknown event type: {}",
other
))),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ResponseCreateBody {
pub model: String,
pub input: ResponsesInput,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ResponsesTool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ResponsesToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
#[serde(default = "default_generate")]
pub generate: bool,
}
fn default_generate() -> bool {
true
}
pub struct ServerEvent;
impl ServerEvent {
pub fn previous_response_not_found(response_id: &str) -> serde_json::Value {
serde_json::json!({
"type": "error",
"code": "previous_response_not_found",
"message": format!("Previous response '{}' not found in connection cache", response_id),
"param": null,
"sequence_number": 0
})
}
pub fn connection_limit_reached() -> serde_json::Value {
serde_json::json!({
"type": "error",
"code": "websocket_connection_limit_reached",
"message": "WebSocket connection has exceeded the 60-minute limit",
"param": null,
"sequence_number": 0
})
}
pub fn invalid_request(message: &str) -> serde_json::Value {
serde_json::json!({
"type": "error",
"code": "invalid_request",
"message": message,
"param": null,
"sequence_number": 0
})
}
pub fn from_error(code: &str, message: &str) -> serde_json::Value {
serde_json::json!({
"type": "error",
"code": code,
"message": message,
"param": null,
"sequence_number": 0
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_event_flat_format() {
let json = r#"{
"type": "response.create",
"model": "gpt-5",
"input": "Hello!"
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert_eq!(response.model, "gpt-5");
assert!(response.generate);
}
}
}
#[test]
fn test_client_event_nested_format() {
let json = r#"{
"type": "response.create",
"response": {
"model": "gpt-5",
"input": "Hello!"
}
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert_eq!(response.model, "gpt-5");
assert!(response.generate);
}
}
}
#[test]
fn test_client_event_flat_with_generate_false() {
let json = r#"{
"type": "response.create",
"model": "gpt-5",
"input": "Hello!",
"generate": false
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert!(!response.generate);
}
}
}
#[test]
fn test_client_event_nested_with_generate_false() {
let json = r#"{
"type": "response.create",
"response": {
"model": "gpt-5",
"input": "Hello!",
"generate": false
}
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert!(!response.generate);
}
}
}
#[test]
fn test_client_event_flat_with_previous_response_id() {
let json = r#"{
"type": "response.create",
"model": "gpt-5",
"input": [
{"role": "user", "content": "Hello!"}
],
"temperature": 0.7,
"previous_response_id": "resp_abc123"
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert_eq!(response.model, "gpt-5");
assert_eq!(response.temperature, Some(0.7));
assert_eq!(
response.previous_response_id,
Some("resp_abc123".to_string())
);
}
}
}
#[test]
fn test_client_event_nested_with_items_input() {
let json = r#"{
"type": "response.create",
"response": {
"model": "gpt-5",
"input": [
{"role": "user", "content": "Hello!"}
],
"temperature": 0.7,
"previous_response_id": "resp_abc123"
}
}"#;
let event: ClientEvent = serde_json::from_str(json).unwrap();
match event {
ClientEvent::ResponseCreate { response } => {
assert_eq!(response.model, "gpt-5");
assert_eq!(response.temperature, Some(0.7));
assert_eq!(
response.previous_response_id,
Some("resp_abc123".to_string())
);
}
}
}
#[test]
fn test_server_event_previous_response_not_found() {
let event = ServerEvent::previous_response_not_found("resp_abc123");
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("previous_response_not_found"));
assert!(json.contains("resp_abc123"));
}
#[test]
fn test_server_event_connection_limit() {
let event = ServerEvent::connection_limit_reached();
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("websocket_connection_limit_reached"));
}
#[test]
fn test_server_event_invalid_request() {
let event = ServerEvent::invalid_request("bad message");
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("invalid_request"));
assert!(json.contains("bad message"));
}
#[test]
fn test_server_event_flat_error_format() {
let event = ServerEvent::invalid_request("test error");
assert_eq!(event["type"], "error");
assert_eq!(event["code"], "invalid_request");
assert_eq!(event["message"], "test error");
assert!(event["param"].is_null());
assert_eq!(event["sequence_number"], 0);
}
}