use crate::ids::{prefixed_id, unix_timestamp};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponsesInput {
Text(String),
Items(Vec<InputItem>),
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum InputItem {
Message {
role: InputRole,
content: MessageContent,
},
FunctionCallOutput { call_id: String, output: String },
}
impl<'de> serde::Deserialize<'de> for InputItem {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let obj = value
.as_object()
.ok_or_else(|| serde::de::Error::custom("expected an object"))?;
match obj.get("type").and_then(|v| v.as_str()) {
Some("message") | None if obj.contains_key("role") => {
let role: InputRole = serde_json::from_value(
obj.get("role")
.cloned()
.ok_or_else(|| serde::de::Error::missing_field("role"))?,
)
.map_err(serde::de::Error::custom)?;
let content: MessageContent = serde_json::from_value(
obj.get("content")
.cloned()
.ok_or_else(|| serde::de::Error::missing_field("content"))?,
)
.map_err(serde::de::Error::custom)?;
Ok(InputItem::Message { role, content })
}
Some("function_call_output") => {
let call_id = obj
.get("call_id")
.and_then(|v| v.as_str())
.ok_or_else(|| serde::de::Error::missing_field("call_id"))?
.to_string();
let output = obj
.get("output")
.and_then(|v| v.as_str())
.ok_or_else(|| serde::de::Error::missing_field("output"))?
.to_string();
Ok(InputItem::FunctionCallOutput { call_id, output })
}
Some(other) => Err(serde::de::Error::unknown_variant(
other,
&["message", "function_call_output"],
)),
None => Err(serde::de::Error::custom(
"missing 'type' or 'role' field in input item",
)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum InputRole {
User,
Assistant,
System,
Developer,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
InputText { text: String },
InputImage { image_url: String },
InputFile {
#[serde(skip_serializing_if = "Option::is_none")]
file_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file_id: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsesRequest {
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(default)]
pub stream: bool,
#[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(default)]
pub background: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponsesTool {
Function {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
parameters: Option<serde_json::Value>,
},
WebSearch {},
FileSearch {},
CodeInterpreter {},
Mcp {
server_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<std::collections::HashMap<String, String>>,
},
ImageGeneration {},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponsesToolChoice {
String(String),
Function { r#type: String, name: String },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
Completed,
Failed,
InProgress,
Queued,
Incomplete,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsesResponse {
pub id: String,
pub object: String,
pub created_at: i64,
pub model: String,
pub status: ResponseStatus,
pub output: Vec<OutputItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponsesUsage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ResponsesError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
}
impl ResponsesResponse {
pub fn new(model: String, content: String, usage: ResponsesUsage) -> Self {
let output_item = OutputItem::Message {
id: prefixed_id("msg_"),
role: OutputRole::Assistant,
status: ItemStatus::Completed,
content: vec![OutputContentPart::OutputText {
text: content.clone(),
annotations: vec![],
}],
};
Self {
id: prefixed_id("resp_"),
object: "response".to_string(),
created_at: unix_timestamp(),
model,
status: ResponseStatus::Completed,
output: vec![output_item],
output_text: Some(content),
usage: Some(usage),
error: None,
metadata: None,
}
}
pub fn warmup(model: String) -> Self {
Self {
id: prefixed_id("resp_"),
object: "response".to_string(),
created_at: unix_timestamp(),
model,
status: ResponseStatus::Completed,
output: vec![],
output_text: None,
usage: None,
error: None,
metadata: None,
}
}
pub fn with_reasoning(
model: String,
content: String,
summary_text: Option<String>,
usage: ResponsesUsage,
) -> Self {
let reasoning_item = OutputItem::Reasoning {
id: prefixed_id("rs_"),
status: ItemStatus::Completed,
summary: summary_text.map(|text| {
vec![ReasoningSummary {
summary_type: "summary_text".to_string(),
text,
}]
}),
};
let message_item = OutputItem::Message {
id: prefixed_id("msg_"),
role: OutputRole::Assistant,
status: ItemStatus::Completed,
content: vec![OutputContentPart::OutputText {
text: content.clone(),
annotations: vec![],
}],
};
Self {
id: prefixed_id("resp_"),
object: "response".to_string(),
created_at: unix_timestamp(),
model,
status: ResponseStatus::Completed,
output: vec![reasoning_item, message_item],
output_text: Some(content),
usage: Some(usage),
error: None,
metadata: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum OutputItem {
Message {
id: String,
role: OutputRole,
status: ItemStatus,
content: Vec<OutputContentPart>,
},
FunctionCall {
id: String,
call_id: String,
name: String,
arguments: String,
status: ItemStatus,
},
Reasoning {
id: String,
status: ItemStatus,
#[serde(skip_serializing_if = "Option::is_none")]
summary: Option<Vec<ReasoningSummary>>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum OutputRole {
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ItemStatus {
Completed,
InProgress,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum OutputContentPart {
OutputText {
text: String,
#[serde(default)]
annotations: Vec<serde_json::Value>,
},
Refusal { refusal: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningSummary {
#[serde(rename = "type")]
pub summary_type: String,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsesUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens_details: Option<OutputTokensDetails>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputTokensDetails {
pub reasoning_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsesError {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
}
impl ResponsesError {
pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
Self {
error_type: error_type.into(),
message: message.into(),
code: None,
}
}
pub fn rate_limit() -> Self {
Self {
error_type: "rate_limit_error".to_string(),
message: "Rate limit exceeded. Please retry after some time.".to_string(),
code: Some("rate_limit_exceeded".to_string()),
}
}
pub fn server_error() -> Self {
Self {
error_type: "server_error".to_string(),
message: "The server had an error processing your request.".to_string(),
code: Some("server_error".to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponsesErrorResponse {
pub error: ResponsesError,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamEvent {
#[serde(rename = "type")]
pub event_type: String,
#[serde(flatten)]
pub data: StreamEventData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StreamEventData {
Response(ResponseEventData),
OutputItem(OutputItemEventData),
ContentPart(ContentPartEventData),
TextDelta(TextDeltaEventData),
Error(ErrorEventData),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseEventData {
pub response: ResponsesResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputItemEventData {
pub output_index: u32,
pub item: OutputItem,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentPartEventData {
pub output_index: u32,
pub content_index: u32,
pub part: OutputContentPart,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextDeltaEventData {
pub output_index: u32,
pub content_index: u32,
pub delta: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub sequence_number: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorEventData {
pub error: ResponsesError,
}
pub struct ResponsesStreamEvent;
impl ResponsesStreamEvent {
pub fn response_created(response: ResponsesResponse, seq: u32) -> String {
let event = serde_json::json!({
"type": "response.created",
"response": response,
"sequence_number": seq
});
format!("event: response.created\ndata: {}\n\n", event)
}
pub fn response_in_progress(response: ResponsesResponse, seq: u32) -> String {
let event = serde_json::json!({
"type": "response.in_progress",
"response": response,
"sequence_number": seq
});
format!("event: response.in_progress\ndata: {}\n\n", event)
}
pub fn output_item_added(output_index: u32, item: &OutputItem, seq: u32) -> String {
let event = serde_json::json!({
"type": "response.output_item.added",
"output_index": output_index,
"item": item,
"sequence_number": seq
});
format!("event: response.output_item.added\ndata: {}\n\n", event)
}
pub fn content_part_added(
output_index: u32,
content_index: u32,
item_id: &str,
part: &OutputContentPart,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.content_part.added",
"output_index": output_index,
"content_index": content_index,
"item_id": item_id,
"part": part,
"sequence_number": seq
});
format!("event: response.content_part.added\ndata: {}\n\n", event)
}
pub fn output_text_delta(
output_index: u32,
content_index: u32,
item_id: &str,
delta: &str,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.output_text.delta",
"output_index": output_index,
"content_index": content_index,
"item_id": item_id,
"delta": delta,
"logprobs": [],
"sequence_number": seq
});
format!("event: response.output_text.delta\ndata: {}\n\n", event)
}
pub fn output_text_done(
output_index: u32,
content_index: u32,
item_id: &str,
text: &str,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.output_text.done",
"output_index": output_index,
"content_index": content_index,
"item_id": item_id,
"text": text,
"logprobs": [],
"sequence_number": seq
});
format!("event: response.output_text.done\ndata: {}\n\n", event)
}
pub fn content_part_done(
output_index: u32,
content_index: u32,
item_id: &str,
part: &OutputContentPart,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.content_part.done",
"output_index": output_index,
"content_index": content_index,
"item_id": item_id,
"part": part,
"sequence_number": seq
});
format!("event: response.content_part.done\ndata: {}\n\n", event)
}
pub fn output_item_done(output_index: u32, item: &OutputItem, seq: u32) -> String {
let event = serde_json::json!({
"type": "response.output_item.done",
"output_index": output_index,
"item": item,
"sequence_number": seq
});
format!("event: response.output_item.done\ndata: {}\n\n", event)
}
pub fn response_completed(response: ResponsesResponse, seq: u32) -> String {
let event = serde_json::json!({
"type": "response.completed",
"response": response,
"sequence_number": seq
});
format!("event: response.completed\ndata: {}\n\n", event)
}
pub fn reasoning_summary_part_added(
output_index: u32,
summary_index: u32,
item_id: &str,
part: &ReasoningSummary,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.reasoning_summary_part.added",
"output_index": output_index,
"summary_index": summary_index,
"item_id": item_id,
"part": part,
"sequence_number": seq
});
format!(
"event: response.reasoning_summary_part.added\ndata: {}\n\n",
event
)
}
pub fn reasoning_summary_text_delta(
output_index: u32,
summary_index: u32,
item_id: &str,
delta: &str,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.reasoning_summary_text.delta",
"output_index": output_index,
"summary_index": summary_index,
"item_id": item_id,
"delta": delta,
"sequence_number": seq
});
format!(
"event: response.reasoning_summary_text.delta\ndata: {}\n\n",
event
)
}
pub fn reasoning_summary_text_done(
output_index: u32,
summary_index: u32,
item_id: &str,
text: &str,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.reasoning_summary_text.done",
"output_index": output_index,
"summary_index": summary_index,
"item_id": item_id,
"text": text,
"sequence_number": seq
});
format!(
"event: response.reasoning_summary_text.done\ndata: {}\n\n",
event
)
}
pub fn reasoning_summary_part_done(
output_index: u32,
summary_index: u32,
item_id: &str,
part: &ReasoningSummary,
seq: u32,
) -> String {
let event = serde_json::json!({
"type": "response.reasoning_summary_part.done",
"output_index": output_index,
"summary_index": summary_index,
"item_id": item_id,
"part": part,
"sequence_number": seq
});
format!(
"event: response.reasoning_summary_part.done\ndata: {}\n\n",
event
)
}
pub fn error(error: ResponsesError, seq: u32) -> String {
let event = serde_json::json!({
"type": "error",
"code": error.code.as_deref().unwrap_or(&error.error_type),
"message": error.message,
"param": null,
"sequence_number": seq
});
format!("event: error\ndata: {}\n\n", event)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_responses_input_text() {
let json = r#""What is the capital of France?""#;
let input: ResponsesInput = serde_json::from_str(json).unwrap();
match input {
ResponsesInput::Text(s) => assert_eq!(s, "What is the capital of France?"),
_ => panic!("Expected Text variant"),
}
}
#[test]
fn test_responses_input_items() {
let json = r#"[
{"type": "message", "role": "user", "content": "Hello!"}
]"#;
let input: ResponsesInput = serde_json::from_str(json).unwrap();
match input {
ResponsesInput::Items(items) => {
assert_eq!(items.len(), 1);
}
_ => panic!("Expected Items variant"),
}
}
#[test]
fn test_responses_input_items_shorthand() {
let json = r#"[
{"role": "user", "content": "Hello!"}
]"#;
let input: ResponsesInput = serde_json::from_str(json).unwrap();
match input {
ResponsesInput::Items(items) => {
assert_eq!(items.len(), 1);
match &items[0] {
InputItem::Message { role, content } => {
assert_eq!(*role, InputRole::User);
match content {
MessageContent::Text(t) => assert_eq!(t, "Hello!"),
_ => panic!("Expected Text content"),
}
}
_ => panic!("Expected Message variant"),
}
}
_ => panic!("Expected Items variant"),
}
}
#[test]
fn test_responses_input_items_shorthand_multi() {
let json = r#"[
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hi!"}
]"#;
let input: ResponsesInput = serde_json::from_str(json).unwrap();
match input {
ResponsesInput::Items(items) => assert_eq!(items.len(), 2),
_ => panic!("Expected Items variant"),
}
}
#[test]
fn test_responses_request_simple() {
let json = r#"{
"model": "gpt-5",
"input": "Tell me a story"
}"#;
let request: ResponsesRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.model, "gpt-5");
assert!(!request.stream);
}
#[test]
fn test_responses_request_with_messages() {
let json = r#"{
"model": "gpt-5",
"input": [
{"type": "message", "role": "user", "content": "Hello!"},
{"type": "message", "role": "assistant", "content": "Hi there!"}
],
"temperature": 0.7,
"stream": true
}"#;
let request: ResponsesRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.model, "gpt-5");
assert_eq!(request.temperature, Some(0.7));
assert!(request.stream);
}
#[test]
fn test_responses_response_new() {
let usage = ResponsesUsage {
input_tokens: 10,
output_tokens: 20,
total_tokens: 30,
output_tokens_details: None,
};
let response = ResponsesResponse::new("gpt-5".to_string(), "Hello!".to_string(), usage);
assert_eq!(response.object, "response");
assert_eq!(response.model, "gpt-5");
assert_eq!(response.status, ResponseStatus::Completed);
assert_eq!(response.output.len(), 1);
assert_eq!(response.output_text, Some("Hello!".to_string()));
}
#[test]
fn test_responses_response_serialization() {
let usage = ResponsesUsage {
input_tokens: 10,
output_tokens: 20,
total_tokens: 30,
output_tokens_details: Some(OutputTokensDetails {
reasoning_tokens: 0,
}),
};
let response =
ResponsesResponse::new("gpt-5".to_string(), "Test response".to_string(), usage);
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"object\":\"response\""));
assert!(json.contains("\"status\":\"completed\""));
assert!(json.contains("\"output_text\":\"Test response\""));
}
#[test]
fn test_content_part_types() {
let json = r#"{"type": "input_text", "text": "Hello"}"#;
let part: ContentPart = serde_json::from_str(json).unwrap();
match part {
ContentPart::InputText { text } => assert_eq!(text, "Hello"),
_ => panic!("Expected InputText variant"),
}
}
#[test]
fn test_stream_event_creation() {
let delta = ResponsesStreamEvent::output_text_delta(0, 0, "msg_123", "Hello", 5);
assert!(delta.contains("event: response.output_text.delta"));
assert!(delta.contains("\"delta\":\"Hello\""));
assert!(delta.contains("\"sequence_number\":5"));
assert!(delta.contains("\"item_id\":\"msg_123\""));
}
#[test]
fn test_error_response() {
let error = ResponsesError::rate_limit();
let error_response = ResponsesErrorResponse { error };
let json = serde_json::to_string(&error_response).unwrap();
assert!(json.contains("\"type\":\"rate_limit_error\""));
}
#[test]
fn test_responses_response_with_reasoning() {
let usage = ResponsesUsage {
input_tokens: 10,
output_tokens: 20,
total_tokens: 90,
output_tokens_details: Some(OutputTokensDetails {
reasoning_tokens: 60,
}),
};
let response = ResponsesResponse::with_reasoning(
"o3".to_string(),
"The answer is 4.".to_string(),
Some("The model considered the arithmetic.".to_string()),
usage,
);
assert_eq!(response.output.len(), 2);
match &response.output[0] {
OutputItem::Reasoning {
id,
status,
summary,
..
} => {
assert!(id.starts_with("rs_"));
assert_eq!(*status, ItemStatus::Completed);
let summary = summary.as_ref().unwrap();
assert_eq!(summary.len(), 1);
assert_eq!(summary[0].summary_type, "summary_text");
assert_eq!(summary[0].text, "The model considered the arithmetic.");
}
_ => panic!("Expected Reasoning variant"),
}
match &response.output[1] {
OutputItem::Message { id, role, .. } => {
assert!(id.starts_with("msg_"));
assert_eq!(*role, OutputRole::Assistant);
}
_ => panic!("Expected Message variant"),
}
}
#[test]
fn test_responses_response_with_reasoning_no_summary() {
let usage = ResponsesUsage {
input_tokens: 10,
output_tokens: 20,
total_tokens: 90,
output_tokens_details: Some(OutputTokensDetails {
reasoning_tokens: 60,
}),
};
let response = ResponsesResponse::with_reasoning(
"o3".to_string(),
"The answer.".to_string(),
None,
usage,
);
assert_eq!(response.output.len(), 2);
match &response.output[0] {
OutputItem::Reasoning { summary, .. } => {
assert!(summary.is_none());
}
_ => panic!("Expected Reasoning variant"),
}
}
#[test]
fn test_reasoning_output_item_serialization() {
let item = OutputItem::Reasoning {
id: "rs_test123".to_string(),
status: ItemStatus::Completed,
summary: Some(vec![ReasoningSummary {
summary_type: "summary_text".to_string(),
text: "Analyzing the problem.".to_string(),
}]),
};
let json = serde_json::to_string(&item).unwrap();
assert!(json.contains("\"type\":\"reasoning\""));
assert!(json.contains("\"id\":\"rs_test123\""));
assert!(json.contains("\"summary_text\""));
assert!(json.contains("Analyzing the problem."));
}
#[test]
fn test_reasoning_stream_event_helpers() {
let summary = ReasoningSummary {
summary_type: "summary_text".to_string(),
text: String::new(),
};
let event = ResponsesStreamEvent::reasoning_summary_part_added(0, 0, "rs_123", &summary, 3);
assert!(event.contains("event: response.reasoning_summary_part.added"));
assert!(event.contains("\"output_index\":0"));
assert!(event.contains("\"item_id\":\"rs_123\""));
assert!(event.contains("\"sequence_number\":3"));
let delta =
ResponsesStreamEvent::reasoning_summary_text_delta(0, 0, "rs_123", "Thinking", 4);
assert!(delta.contains("event: response.reasoning_summary_text.delta"));
assert!(delta.contains("\"delta\":\"Thinking\""));
assert!(delta.contains("\"sequence_number\":4"));
let done =
ResponsesStreamEvent::reasoning_summary_text_done(0, 0, "rs_123", "Full summary.", 5);
assert!(done.contains("event: response.reasoning_summary_text.done"));
assert!(done.contains("\"text\":\"Full summary.\""));
assert!(done.contains("\"sequence_number\":5"));
let part_done =
ResponsesStreamEvent::reasoning_summary_part_done(0, 0, "rs_123", &summary, 6);
assert!(part_done.contains("event: response.reasoning_summary_part.done"));
assert!(part_done.contains("\"sequence_number\":6"));
}
#[test]
fn test_reasoning_config_deserialization() {
let json = r#"{
"model": "o3",
"input": "Hello",
"reasoning": {
"effort": "high",
"summary": "auto"
}
}"#;
let request: ResponsesRequest = serde_json::from_str(json).unwrap();
let reasoning = request.reasoning.unwrap();
assert_eq!(reasoning.effort, Some("high".to_string()));
assert_eq!(reasoning.summary, Some("auto".to_string()));
}
}