use anyhow::{Context as _, Result, anyhow};use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};use http_client::{ AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt,};use serde::{Deserialize, Serialize, ser::SerializeSeq as _};use serde_json::Value;use std::sync::Arc; use crate::{ReasoningEffort, RequestError, Role, ServiceTier, ToolChoice};use language_model_core::{ CompactedContext, LanguageModelProviderId, ProviderCompactionState, SharedString,}; pub const COMPACTION_STATE_FORMAT: &str = "openai.responses.input-items.v1"; #[derive(Serialize, Debug)]pub struct Request { pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub instructions: Option<String>, #[serde(skip_serializing_if = "ResponseInput::is_empty")] pub input: ResponseInput, #[serde(skip_serializing_if = "Vec::is_empty")] pub include: Vec<ResponseIncludable>, #[serde(default)] pub stream: bool, #[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<u64>, #[serde(skip_serializing_if = "Option::is_none")] pub parallel_tool_calls: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub tool_choice: Option<ToolChoice>, #[serde(skip_serializing_if = "Vec::is_empty")] pub tools: Vec<ToolDefinition>, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub reasoning: Option<ReasoningConfig>, #[serde(skip_serializing_if = "Option::is_none")] pub store: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option<ServiceTier>, #[serde(default, skip_serializing_if = "Option::is_none")] pub context_management: Option<Vec<ContextManagement>>,} impl Request { pub fn into_compact_request(self) -> CompactRequest { CompactRequest { model: self.model, instructions: self.instructions, input: self.input, prompt_cache_key: self.prompt_cache_key, service_tier: self.service_tier, } }} #[derive(Serialize, Debug)]pub struct CompactRequest { pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub instructions: Option<String>, pub input: ResponseInput, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option<ServiceTier>,} #[derive(Deserialize, Debug)]pub struct CompactedResponse { pub id: String, pub created_at: u64, pub object: String, pub output: Vec<Value>, pub usage: ResponseUsage,} impl CompactedResponse { pub fn into_compacted_context( self, owner: LanguageModelProviderId, ) -> Result<CompactedContext> { Ok(CompactedContext::ProviderState( provider_compaction_state_from_items(owner, self.output)?, )) }} /// Packages a canonical replacement window into opaque provider state owned by/// `owner`.////// Several backends speak the OpenAI Responses protocol (OpenAI itself, Zed/// Cloud's OpenAI models, OpenAI-compatible endpoints, and others), but their/// encrypted compaction items are not interchangeable: only the backend that/// produced an item can decrypt it. The owner recorded here is what/// [`provider_compaction_items`] later compares against, so it must identify/// the backend whose infrastructure produced the items, not merely the wire/// protocol.pub fn provider_compaction_state_from_items( owner: LanguageModelProviderId, items: Vec<Value>,) -> Result<ProviderCompactionState> { validate_compaction_items(&items)?; Ok(ProviderCompactionState::new( owner, SharedString::new_static(COMPACTION_STATE_FORMAT), serde_json::to_string(&items)?, ))} /// Recovers the canonical replacement window from `state` if it is owned by/// `owner`, or `None` when the state belongs to a different backend and the/// caller should fall back to replaying the full transcript.pub fn provider_compaction_items( state: &ProviderCompactionState, owner: &LanguageModelProviderId,) -> Result<Option<Vec<Value>>> { if state.provider_id() != owner { return Ok(None); } if state.format() != COMPACTION_STATE_FORMAT { return Err(anyhow!( "unsupported OpenAI compaction state format: {}", state.format() )); } let items = serde_json::from_str::<Vec<Value>>(state.payload()) .context("malformed OpenAI compaction state payload")?; validate_compaction_items(&items)?; Ok(Some(items))} fn validate_compaction_items(items: &[Value]) -> Result<()> { if items.is_empty() { return Err(anyhow!("OpenAI returned an empty compaction output")); } if !items.iter().any(|item| { item.get("type") .and_then(Value::as_str) .is_some_and(|item_type| item_type == "compaction") }) { return Err(anyhow!( "OpenAI compaction output did not contain a compaction item" )); } Ok(())} #[derive(Debug, Default)]pub struct ResponseInput { provider_items: Vec<Value>, generated_items: Vec<ResponseInputItem>,} impl ResponseInput { pub fn new(provider_items: Vec<Value>, generated_items: Vec<ResponseInputItem>) -> Self { Self { provider_items, generated_items, } } pub fn is_empty(&self) -> bool { self.provider_items.is_empty() && self.generated_items.is_empty() } /// Filters only the items this crate generated from the request. /// /// Provider items are a canonical replacement window that must be replayed /// verbatim, so they are exempt from filtering. Callers that rewrite the /// input to satisfy backend-specific requirements (and therefore can't /// tolerate arbitrary items inside a replayed window) should not accept /// provider-native compaction state in the first place. pub fn retain(&mut self, predicate: impl FnMut(&ResponseInputItem) -> bool) { self.generated_items.retain(predicate); }} impl Serialize for ResponseInput { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { let mut sequence = serializer .serialize_seq(Some(self.provider_items.len() + self.generated_items.len()))?; for item in &self.provider_items { sequence.serialize_element(item)?; } for item in &self.generated_items { sequence.serialize_element(item)?; } sequence.end() }} /// Server-side context management configuration.////// <https://developers.openai.com/api/docs/guides/compaction>#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ContextManagement { Compaction { compact_threshold: u64 },} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]#[serde(rename_all = "snake_case")]pub enum ResponseIncludable { #[serde(rename = "reasoning.encrypted_content")] ReasoningEncryptedContent,} #[derive(Debug, Serialize, Deserialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ResponseInputItem { Message(ResponseMessageItem), FunctionCall(ResponseFunctionCallItem), FunctionCallOutput(ResponseFunctionCallOutputItem), CustomToolCall(ResponseCustomToolCallItem), CustomToolCallOutput(ResponseCustomToolCallOutputItem), Reasoning(ResponseReasoningInputItem), Compaction(ResponseCompactionItem),} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct ResponseCompactionItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<Arc<str>>, pub encrypted_content: Arc<str>,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseMessageItem { pub role: Role, pub content: Vec<ResponseInputContent>, #[serde(default, skip_serializing_if = "Option::is_none")] pub phase: Option<String>,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseFunctionCallItem { pub call_id: String, pub name: String, pub arguments: String,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseFunctionCallOutputItem { pub call_id: String, pub output: ResponseFunctionCallOutputContent,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseCustomToolCallItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<String>, pub call_id: String, pub name: String, pub input: String,} #[derive(Debug, Serialize, Deserialize)]pub struct ResponseCustomToolCallOutputItem { pub call_id: String, pub output: ResponseFunctionCallOutputContent,} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct ResponseReasoningInputItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<String>, #[serde(default)] pub summary: Vec<ResponseReasoningSummaryPart>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub content: Vec<Value>, #[serde(default, skip_serializing_if = "Option::is_none")] pub encrypted_content: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] pub status: Option<String>,} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ResponseReasoningSummaryPart { SummaryText { text: String },} #[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum ResponseFunctionCallOutputContent { List(Vec<ResponseInputContent>), Text(String),} #[derive(Debug, Clone, Serialize, Deserialize)]#[serde(tag = "type")]pub enum ResponseInputContent { #[serde(rename = "input_text")] Text { text: String }, #[serde(rename = "input_image")] Image { image_url: String }, #[serde(rename = "output_text")] OutputText { text: String, #[serde(default)] annotations: Vec<serde_json::Value>, }, #[serde(rename = "refusal")] Refusal { refusal: String },} #[derive(Serialize, Debug)]pub struct ReasoningConfig { pub effort: ReasoningEffort, #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option<ReasoningSummaryMode>,} #[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]#[serde(rename_all = "lowercase")]pub enum ReasoningSummaryMode { Auto, Concise, Detailed,} #[derive(Serialize, Deserialize, Debug)]#[serde(tag = "type", rename_all = "snake_case")]pub enum ToolDefinition { Function { name: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] parameters: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] strict: Option<bool>, }, Custom { name: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] format: Option<CustomToolFormat>, },} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]#[serde(tag = "type", rename_all = "snake_case")]pub enum CustomToolFormat { Text, Grammar { syntax: CustomToolGrammarSyntax, definition: String, },} #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]#[serde(rename_all = "lowercase")]pub enum CustomToolGrammarSyntax { Lark, Regex,} #[derive(Serialize, Deserialize, Debug, Clone)]pub struct ResponseError { #[serde(default)] pub code: Option<String>, pub message: String, #[serde(default)] pub param: Option<Value>,} /// Payload of the top-level `error` SSE event from the Responses API.////// OpenAI's spec documents the error fields as being at the top level of the/// event, but in practice the API often nests them under an `error` object.#[derive(Deserialize, Debug, Clone, Default)]pub struct GenericStreamErrorPayload { #[serde(flatten)] top_level: PartialResponseError, #[serde(default)] error: Option<PartialResponseError>,} #[derive(Deserialize, Debug, Clone, Default)]struct PartialResponseError { #[serde(default)] code: Option<String>, #[serde(default)] message: Option<String>, #[serde(default)] param: Option<Value>,} impl GenericStreamErrorPayload { pub fn into_response_error(self) -> ResponseError { let nested = self.error.unwrap_or_default(); ResponseError { code: self.top_level.code.or(nested.code), message: self .top_level .message .or(nested.message) .unwrap_or_default(), param: self.top_level.param.or(nested.param), } }} #[derive(Deserialize, Debug)]#[serde(tag = "type")]pub enum StreamEvent { #[serde(rename = "response.created")] Created { response: ResponseSummary }, #[serde(rename = "response.in_progress")] InProgress { response: ResponseSummary }, #[serde(rename = "response.output_item.added")] OutputItemAdded { output_index: usize, #[serde(default)] sequence_number: Option<u64>, item: ResponseOutputItem, }, #[serde(rename = "response.output_item.done")] OutputItemDone { output_index: usize, #[serde(default)] sequence_number: Option<u64>, item: ResponseOutputItem, }, #[serde(rename = "response.content_part.added")] ContentPartAdded { item_id: String, output_index: usize, content_index: usize, part: Value, }, #[serde(rename = "response.content_part.done")] ContentPartDone { item_id: String, output_index: usize, content_index: usize, part: Value, }, #[serde(rename = "response.output_text.delta")] OutputTextDelta { item_id: String, output_index: usize, #[serde(default)] content_index: Option<usize>, delta: String, }, #[serde(rename = "response.output_text.done")] OutputTextDone { item_id: String, output_index: usize, #[serde(default)] content_index: Option<usize>, text: String, }, #[serde(rename = "response.refusal.delta")] RefusalDelta { item_id: String, output_index: usize, content_index: usize, delta: String, #[serde(default)] sequence_number: Option<u64>, }, #[serde(rename = "response.refusal.done")] RefusalDone { item_id: String, output_index: usize, content_index: usize, refusal: String, #[serde(default)] sequence_number: Option<u64>, }, #[serde(rename = "response.reasoning_summary_part.added")] ReasoningSummaryPartAdded { item_id: String, output_index: usize, summary_index: usize, }, #[serde(rename = "response.reasoning_summary_text.delta")] ReasoningSummaryTextDelta { item_id: String, output_index: usize, delta: String, }, #[serde(rename = "response.reasoning_summary_text.done")] ReasoningSummaryTextDone {Lines 1–500 of 1264. Load more
use anyhow::{Context as _, Result, anyhow}; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; use http_client::{ AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt, }; use serde::{Deserialize, Serialize, ser::SerializeSeq as _}; use serde_json::Value; use std::sync::Arc; use crate::{ReasoningEffort, RequestError, Role, ServiceTier, ToolChoice}; use language_model_core::{ CompactedContext, LanguageModelProviderId, ProviderCompactionState, SharedString, }; pub const COMPACTION_STATE_FORMAT: &str = "openai.responses.input-items.v1"; #[derive(Serialize, Debug)] pub struct Request { pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub instructions: Option<String>, #[serde(skip_serializing_if = "ResponseInput::is_empty")] pub input: ResponseInput, #[serde(skip_serializing_if = "Vec::is_empty")] pub include: Vec<ResponseIncludable>, #[serde(default)] pub stream: bool, #[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<u64>, #[serde(skip_serializing_if = "Option::is_none")] pub parallel_tool_calls: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub tool_choice: Option<ToolChoice>, #[serde(skip_serializing_if = "Vec::is_empty")] pub tools: Vec<ToolDefinition>, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub reasoning: Option<ReasoningConfig>, #[serde(skip_serializing_if = "Option::is_none")] pub store: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option<ServiceTier>, #[serde(default, skip_serializing_if = "Option::is_none")] pub context_management: Option<Vec<ContextManagement>>, } impl Request { pub fn into_compact_request(self) -> CompactRequest { CompactRequest { model: self.model, instructions: self.instructions, input: self.input, prompt_cache_key: self.prompt_cache_key, service_tier: self.service_tier, } } } #[derive(Serialize, Debug)] pub struct CompactRequest { pub model: String, #[serde(skip_serializing_if = "Option::is_none")] pub instructions: Option<String>, pub input: ResponseInput, #[serde(skip_serializing_if = "Option::is_none")] pub prompt_cache_key: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub service_tier: Option<ServiceTier>, } #[derive(Deserialize, Debug)] pub struct CompactedResponse { pub id: String, pub created_at: u64, pub object: String, pub output: Vec<Value>, pub usage: ResponseUsage, } impl CompactedResponse { pub fn into_compacted_context( self, owner: LanguageModelProviderId, ) -> Result<CompactedContext> { Ok(CompactedContext::ProviderState( provider_compaction_state_from_items(owner, self.output)?, )) } } /// Packages a canonical replacement window into opaque provider state owned by /// `owner`. /// /// Several backends speak the OpenAI Responses protocol (OpenAI itself, Zed /// Cloud's OpenAI models, OpenAI-compatible endpoints, and others), but their /// encrypted compaction items are not interchangeable: only the backend that /// produced an item can decrypt it. The owner recorded here is what /// [`provider_compaction_items`] later compares against, so it must identify /// the backend whose infrastructure produced the items, not merely the wire /// protocol. pub fn provider_compaction_state_from_items( owner: LanguageModelProviderId, items: Vec<Value>, ) -> Result<ProviderCompactionState> { validate_compaction_items(&items)?; Ok(ProviderCompactionState::new( owner, SharedString::new_static(COMPACTION_STATE_FORMAT), serde_json::to_string(&items)?, )) } /// Recovers the canonical replacement window from `state` if it is owned by /// `owner`, or `None` when the state belongs to a different backend and the /// caller should fall back to replaying the full transcript. pub fn provider_compaction_items( state: &ProviderCompactionState, owner: &LanguageModelProviderId, ) -> Result<Option<Vec<Value>>> { if state.provider_id() != owner { return Ok(None); } if state.format() != COMPACTION_STATE_FORMAT { return Err(anyhow!( "unsupported OpenAI compaction state format: {}", state.format() )); } let items = serde_json::from_str::<Vec<Value>>(state.payload()) .context("malformed OpenAI compaction state payload")?; validate_compaction_items(&items)?; Ok(Some(items)) } fn validate_compaction_items(items: &[Value]) -> Result<()> { if items.is_empty() { return Err(anyhow!("OpenAI returned an empty compaction output")); } if !items.iter().any(|item| { item.get("type") .and_then(Value::as_str) .is_some_and(|item_type| item_type == "compaction") }) { return Err(anyhow!( "OpenAI compaction output did not contain a compaction item" )); } Ok(()) } #[derive(Debug, Default)] pub struct ResponseInput { provider_items: Vec<Value>, generated_items: Vec<ResponseInputItem>, } impl ResponseInput { pub fn new(provider_items: Vec<Value>, generated_items: Vec<ResponseInputItem>) -> Self { Self { provider_items, generated_items, } } pub fn is_empty(&self) -> bool { self.provider_items.is_empty() && self.generated_items.is_empty() } /// Filters only the items this crate generated from the request. /// /// Provider items are a canonical replacement window that must be replayed /// verbatim, so they are exempt from filtering. Callers that rewrite the /// input to satisfy backend-specific requirements (and therefore can't /// tolerate arbitrary items inside a replayed window) should not accept /// provider-native compaction state in the first place. pub fn retain(&mut self, predicate: impl FnMut(&ResponseInputItem) -> bool) { self.generated_items.retain(predicate); } } impl Serialize for ResponseInput { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { let mut sequence = serializer .serialize_seq(Some(self.provider_items.len() + self.generated_items.len()))?; for item in &self.provider_items { sequence.serialize_element(item)?; } for item in &self.generated_items { sequence.serialize_element(item)?; } sequence.end() } } /// Server-side context management configuration. /// /// <https://developers.openai.com/api/docs/guides/compaction> #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ContextManagement { Compaction { compact_threshold: u64 }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ResponseIncludable { #[serde(rename = "reasoning.encrypted_content")] ReasoningEncryptedContent, } #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ResponseInputItem { Message(ResponseMessageItem), FunctionCall(ResponseFunctionCallItem), FunctionCallOutput(ResponseFunctionCallOutputItem), CustomToolCall(ResponseCustomToolCallItem), CustomToolCallOutput(ResponseCustomToolCallOutputItem), Reasoning(ResponseReasoningInputItem), Compaction(ResponseCompactionItem), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ResponseCompactionItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<Arc<str>>, pub encrypted_content: Arc<str>, } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseMessageItem { pub role: Role, pub content: Vec<ResponseInputContent>, #[serde(default, skip_serializing_if = "Option::is_none")] pub phase: Option<String>, } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseFunctionCallItem { pub call_id: String, pub name: String, pub arguments: String, } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseFunctionCallOutputItem { pub call_id: String, pub output: ResponseFunctionCallOutputContent, } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseCustomToolCallItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<String>, pub call_id: String, pub name: String, pub input: String, } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseCustomToolCallOutputItem { pub call_id: String, pub output: ResponseFunctionCallOutputContent, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct ResponseReasoningInputItem { #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option<String>, #[serde(default)] pub summary: Vec<ResponseReasoningSummaryPart>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub content: Vec<Value>, #[serde(default, skip_serializing_if = "Option::is_none")] pub encrypted_content: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] pub status: Option<String>, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ResponseReasoningSummaryPart { SummaryText { text: String }, } #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum ResponseFunctionCallOutputContent { List(Vec<ResponseInputContent>), Text(String), } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum ResponseInputContent { #[serde(rename = "input_text")] Text { text: String }, #[serde(rename = "input_image")] Image { image_url: String }, #[serde(rename = "output_text")] OutputText { text: String, #[serde(default)] annotations: Vec<serde_json::Value>, }, #[serde(rename = "refusal")] Refusal { refusal: String }, } #[derive(Serialize, Debug)] pub struct ReasoningConfig { pub effort: ReasoningEffort, #[serde(skip_serializing_if = "Option::is_none")] pub summary: Option<ReasoningSummaryMode>, } #[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ReasoningSummaryMode { Auto, Concise, Detailed, } #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ToolDefinition { Function { name: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] parameters: Option<Value>, #[serde(skip_serializing_if = "Option::is_none")] strict: Option<bool>, }, Custom { name: String, #[serde(skip_serializing_if = "Option::is_none")] description: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] format: Option<CustomToolFormat>, }, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(tag = "type", rename_all = "snake_case")] pub enum CustomToolFormat { Text, Grammar { syntax: CustomToolGrammarSyntax, definition: String, }, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum CustomToolGrammarSyntax { Lark, Regex, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ResponseError { #[serde(default)] pub code: Option<String>, pub message: String, #[serde(default)] pub param: Option<Value>, } /// Payload of the top-level `error` SSE event from the Responses API. /// /// OpenAI's spec documents the error fields as being at the top level of the /// event, but in practice the API often nests them under an `error` object. #[derive(Deserialize, Debug, Clone, Default)] pub struct GenericStreamErrorPayload { #[serde(flatten)] top_level: PartialResponseError, #[serde(default)] error: Option<PartialResponseError>, } #[derive(Deserialize, Debug, Clone, Default)] struct PartialResponseError { #[serde(default)] code: Option<String>, #[serde(default)] message: Option<String>, #[serde(default)] param: Option<Value>, } impl GenericStreamErrorPayload { pub fn into_response_error(self) -> ResponseError { let nested = self.error.unwrap_or_default(); ResponseError { code: self.top_level.code.or(nested.code), message: self .top_level .message .or(nested.message) .unwrap_or_default(), param: self.top_level.param.or(nested.param), } } } #[derive(Deserialize, Debug)] #[serde(tag = "type")] pub enum StreamEvent { #[serde(rename = "response.created")] Created { response: ResponseSummary }, #[serde(rename = "response.in_progress")] InProgress { response: ResponseSummary }, #[serde(rename = "response.output_item.added")] OutputItemAdded { output_index: usize, #[serde(default)] sequence_number: Option<u64>, item: ResponseOutputItem, }, #[serde(rename = "response.output_item.done")] OutputItemDone { output_index: usize, #[serde(default)] sequence_number: Option<u64>, item: ResponseOutputItem, }, #[serde(rename = "response.content_part.added")] ContentPartAdded { item_id: String, output_index: usize, content_index: usize, part: Value, }, #[serde(rename = "response.content_part.done")] ContentPartDone { item_id: String, output_index: usize, content_index: usize, part: Value, }, #[serde(rename = "response.output_text.delta")] OutputTextDelta { item_id: String, output_index: usize, #[serde(default)] content_index: Option<usize>, delta: String, }, #[serde(rename = "response.output_text.done")] OutputTextDone { item_id: String, output_index: usize, #[serde(default)] content_index: Option<usize>, text: String, }, #[serde(rename = "response.refusal.delta")] RefusalDelta { item_id: String, output_index: usize, content_index: usize, delta: String, #[serde(default)] sequence_number: Option<u64>, }, #[serde(rename = "response.refusal.done")] RefusalDone { item_id: String, output_index: usize, content_index: usize, refusal: String, #[serde(default)] sequence_number: Option<u64>, }, #[serde(rename = "response.reasoning_summary_part.added")] ReasoningSummaryPartAdded { item_id: String, output_index: usize, summary_index: usize, }, #[serde(rename = "response.reasoning_summary_text.delta")] ReasoningSummaryTextDelta { item_id: String, output_index: usize, delta: String, }, #[serde(rename = "response.reasoning_summary_text.done")] ReasoningSummaryTextDone {