use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CreateResponseRequest {
pub model: String,
pub input: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ResponseTool>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_body: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseTool {
#[serde(rename = "type")]
pub tool_type: String,
#[serde(flatten)]
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseObject {
pub id: String,
pub object: String,
pub created_at: u64,
pub model: String,
pub status: String,
pub output: Vec<ResponseOutputItem>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponseUsage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseOutputItem {
#[serde(rename = "type")]
pub item_type: String,
#[serde(flatten)]
pub content: serde_json::Value,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResponseUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub total_tokens: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseOutputTextDelta {
pub item_id: String,
pub output_index: u32,
pub content_index: u32,
pub delta: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseFunctionCallArgumentsDelta {
pub item_id: String,
pub output_index: u32,
pub delta: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseOutputItemEvent {
pub output_index: u32,
pub item: ResponseOutputItem,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseWebSearchCallEvent {
pub output_index: u32,
pub item_id: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResponseCompletedEvent {
pub response: ResponseObject,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnknownResponseStreamEvent {
pub event_type: String,
pub raw: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResponseStreamEvent {
OutputTextDelta(ResponseOutputTextDelta),
FunctionCallArgumentsDelta(ResponseFunctionCallArgumentsDelta),
OutputItemAdded(ResponseOutputItemEvent),
OutputItemDone(ResponseOutputItemEvent),
WebSearchCallInProgress(ResponseWebSearchCallEvent),
WebSearchCallSearching(ResponseWebSearchCallEvent),
WebSearchCallCompleted(ResponseWebSearchCallEvent),
Completed(ResponseCompletedEvent),
Incomplete(ResponseCompletedEvent),
Failed(ResponseCompletedEvent),
Unknown(UnknownResponseStreamEvent),
}
impl Serialize for ResponseStreamEvent {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
#[serde(tag = "type")]
enum Tagged<'a> {
#[serde(rename = "response.output_text.delta")]
OutputTextDelta(&'a ResponseOutputTextDelta),
#[serde(rename = "response.function_call_arguments.delta")]
FunctionCallArgumentsDelta(&'a ResponseFunctionCallArgumentsDelta),
#[serde(rename = "response.output_item.added")]
OutputItemAdded(&'a ResponseOutputItemEvent),
#[serde(rename = "response.output_item.done")]
OutputItemDone(&'a ResponseOutputItemEvent),
#[serde(rename = "response.web_search_call.in_progress")]
WebSearchCallInProgress(&'a ResponseWebSearchCallEvent),
#[serde(rename = "response.web_search_call.searching")]
WebSearchCallSearching(&'a ResponseWebSearchCallEvent),
#[serde(rename = "response.web_search_call.completed")]
WebSearchCallCompleted(&'a ResponseWebSearchCallEvent),
#[serde(rename = "response.completed")]
Completed(&'a ResponseCompletedEvent),
#[serde(rename = "response.incomplete")]
Incomplete(&'a ResponseCompletedEvent),
#[serde(rename = "response.failed")]
Failed(&'a ResponseCompletedEvent),
}
match self {
Self::OutputTextDelta(event) => Tagged::OutputTextDelta(event).serialize(serializer),
Self::FunctionCallArgumentsDelta(event) => Tagged::FunctionCallArgumentsDelta(event).serialize(serializer),
Self::OutputItemAdded(event) => Tagged::OutputItemAdded(event).serialize(serializer),
Self::OutputItemDone(event) => Tagged::OutputItemDone(event).serialize(serializer),
Self::WebSearchCallInProgress(event) => Tagged::WebSearchCallInProgress(event).serialize(serializer),
Self::WebSearchCallSearching(event) => Tagged::WebSearchCallSearching(event).serialize(serializer),
Self::WebSearchCallCompleted(event) => Tagged::WebSearchCallCompleted(event).serialize(serializer),
Self::Completed(event) => Tagged::Completed(event).serialize(serializer),
Self::Incomplete(event) => Tagged::Incomplete(event).serialize(serializer),
Self::Failed(event) => Tagged::Failed(event).serialize(serializer),
Self::Unknown(event) => event.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for ResponseStreamEvent {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
let event_type = value
.get("type")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_owned();
let variant = match event_type.as_str() {
"response.output_text.delta" => serde_json::from_value(value.clone()).map(Self::OutputTextDelta),
"response.function_call_arguments.delta" => {
serde_json::from_value(value.clone()).map(Self::FunctionCallArgumentsDelta)
}
"response.output_item.added" => serde_json::from_value(value.clone()).map(Self::OutputItemAdded),
"response.output_item.done" => serde_json::from_value(value.clone()).map(Self::OutputItemDone),
"response.web_search_call.in_progress" => {
serde_json::from_value(value.clone()).map(Self::WebSearchCallInProgress)
}
"response.web_search_call.searching" => {
serde_json::from_value(value.clone()).map(Self::WebSearchCallSearching)
}
"response.web_search_call.completed" => {
serde_json::from_value(value.clone()).map(Self::WebSearchCallCompleted)
}
"response.completed" => serde_json::from_value(value.clone()).map(Self::Completed),
"response.incomplete" => serde_json::from_value(value.clone()).map(Self::Incomplete),
"response.failed" => serde_json::from_value(value.clone()).map(Self::Failed),
_ => return Ok(Self::Unknown(UnknownResponseStreamEvent { event_type, raw: value })),
};
variant.map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod stream_event_tests {
use super::*;
#[test]
fn output_text_delta_extracts_exact_delta_value() {
let payload = serde_json::json!({
"type": "response.output_text.delta",
"item_id": "msg_1",
"output_index": 0,
"content_index": 0,
"delta": "Hello, world",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::OutputTextDelta(delta) => {
assert_eq!(delta.delta, "Hello, world");
assert_eq!(delta.item_id, "msg_1");
assert_eq!(delta.output_index, 0);
assert_eq!(delta.content_index, 0);
}
other => panic!("expected OutputTextDelta, got {other:?}"),
}
}
#[test]
fn function_call_arguments_delta_extracts_exact_delta_value() {
let payload = serde_json::json!({
"type": "response.function_call_arguments.delta",
"item_id": "fc_1",
"output_index": 2,
"delta": "{\"city\":",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::FunctionCallArgumentsDelta(delta) => {
assert_eq!(delta.delta, "{\"city\":");
assert_eq!(delta.item_id, "fc_1");
assert_eq!(delta.output_index, 2);
}
other => panic!("expected FunctionCallArgumentsDelta, got {other:?}"),
}
}
#[test]
fn output_item_added_carries_the_output_item() {
let payload = serde_json::json!({
"type": "response.output_item.added",
"output_index": 0,
"item": { "type": "message", "id": "msg_1" },
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::OutputItemAdded(added) => {
assert_eq!(added.output_index, 0);
assert_eq!(added.item.item_type, "message");
assert_eq!(added.item.content.get("id").and_then(|v| v.as_str()), Some("msg_1"));
}
other => panic!("expected OutputItemAdded, got {other:?}"),
}
}
#[test]
fn output_item_done_carries_the_output_item() {
let payload = serde_json::json!({
"type": "response.output_item.done",
"output_index": 1,
"item": { "type": "message", "id": "msg_2" },
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::OutputItemDone(done) => {
assert_eq!(done.output_index, 1);
assert_eq!(done.item.item_type, "message");
assert_eq!(done.item.content.get("id").and_then(|v| v.as_str()), Some("msg_2"));
}
other => panic!("expected OutputItemDone, got {other:?}"),
}
}
#[test]
fn web_search_call_in_progress_carries_item_id() {
let payload = serde_json::json!({
"type": "response.web_search_call.in_progress",
"output_index": 0,
"item_id": "ws_1",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::WebSearchCallInProgress(call) => {
assert_eq!(call.item_id, "ws_1");
assert_eq!(call.output_index, 0);
}
other => panic!("expected WebSearchCallInProgress, got {other:?}"),
}
}
#[test]
fn web_search_call_searching_carries_item_id() {
let payload = serde_json::json!({
"type": "response.web_search_call.searching",
"output_index": 0,
"item_id": "ws_1",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::WebSearchCallSearching(call) => {
assert_eq!(call.item_id, "ws_1");
assert_eq!(call.output_index, 0);
}
other => panic!("expected WebSearchCallSearching, got {other:?}"),
}
}
#[test]
fn web_search_call_completed_carries_item_id() {
let payload = serde_json::json!({
"type": "response.web_search_call.completed",
"output_index": 0,
"item_id": "ws_1",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::WebSearchCallCompleted(call) => {
assert_eq!(call.item_id, "ws_1");
assert_eq!(call.output_index, 0);
}
other => panic!("expected WebSearchCallCompleted, got {other:?}"),
}
}
#[test]
fn completed_carries_the_final_response_object() {
let payload = serde_json::json!({
"type": "response.completed",
"response": {
"id": "resp_1",
"object": "response",
"created_at": 1_700_000_000,
"model": "gpt-4o",
"status": "completed",
"output": [],
},
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::Completed(completed) => {
assert_eq!(completed.response.id, "resp_1");
assert_eq!(completed.response.status, "completed");
}
other => panic!("expected Completed, got {other:?}"),
}
}
#[test]
fn failed_carries_the_final_response_object() {
let payload = serde_json::json!({
"type": "response.failed",
"response": {
"id": "resp_2",
"object": "response",
"created_at": 1_700_000_000,
"model": "gpt-4o",
"status": "failed",
"output": [],
"error": { "message": "rate limited" },
},
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::Failed(failed) => {
assert_eq!(failed.response.id, "resp_2");
assert_eq!(failed.response.status, "failed");
assert_eq!(
failed
.response
.error
.as_ref()
.and_then(|e| e.get("message"))
.and_then(|v| v.as_str()),
Some("rate limited")
);
}
other => panic!("expected Failed, got {other:?}"),
}
}
#[test]
fn unknown_event_type_deserializes_to_catch_all_variant() {
let payload = serde_json::json!({
"type": "response.some_future_event",
"foo": "bar",
});
let event: ResponseStreamEvent = serde_json::from_value(payload).expect("should deserialize");
match event {
ResponseStreamEvent::Unknown(unknown) => {
assert_eq!(unknown.event_type, "response.some_future_event");
assert_eq!(unknown.raw.get("foo").and_then(|v| v.as_str()), Some("bar"));
}
other => panic!("expected Unknown, got {other:?}"),
}
}
}