use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageRole {
User,
Assistant,
System,
Developer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ItemStatus {
InProgress,
Completed,
Incomplete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningEffort {
None,
Low,
Medium,
High,
#[serde(rename = "xhigh")]
XHigh,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningSummary {
Concise,
Detailed,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoiceMode {
None,
Auto,
Required,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ServiceTier {
Auto,
Default,
Flex,
Priority,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Truncation {
Auto,
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Low,
High,
Auto,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Verbosity {
Low,
Medium,
High,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResponseStatus {
Completed,
Failed,
Cancelled,
InProgress,
Queued,
Incomplete,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputTextContent {
#[serde(rename = "type")]
pub type_: String,
pub text: String,
}
impl InputTextContent {
pub fn new(text: impl Into<String>) -> Self {
Self {
type_: "input_text".to_string(),
text: text.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputImageContent {
#[serde(rename = "type")]
pub type_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<ImageDetail>,
}
impl InputImageContent {
pub fn new(url: impl Into<String>) -> Self {
Self {
type_: "input_image".to_string(),
image_url: Some(url.into()),
detail: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputAudioContent {
#[serde(rename = "type")]
pub type_: String,
pub input_audio: InputAudioData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputAudioData {
pub data: String,
pub format: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputFileContent {
#[serde(rename = "type")]
pub type_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputVideoContent {
#[serde(rename = "type")]
pub type_: String,
pub video_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentPart {
#[serde(rename = "input_text")]
InputText { text: String },
#[serde(rename = "input_image")]
InputImage {
#[serde(skip_serializing_if = "Option::is_none")]
image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
detail: Option<ImageDetail>,
},
#[serde(rename = "input_audio")]
InputAudio { input_audio: InputAudioData },
#[serde(rename = "input_file")]
InputFile {
#[serde(skip_serializing_if = "Option::is_none")]
filename: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file_url: Option<String>,
},
#[serde(rename = "input_video")]
InputVideo { video_url: String },
#[serde(rename = "output_text")]
OutputText {
text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
annotations: Vec<UrlCitation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
logprobs: Vec<LogProb>,
},
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "summary_text")]
SummaryText { text: String },
#[serde(rename = "reasoning_text")]
ReasoningText { text: String },
#[serde(rename = "refusal")]
Refusal { refusal: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageItem {
#[serde(rename = "type")]
pub type_: String,
pub role: String,
pub content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
}
impl MessageItem {
pub fn new(role: &str, content: MessageContent) -> Self {
Self {
type_: "message".to_string(),
role: role.to_string(),
content,
id: None,
status: None,
}
}
pub fn user(text: impl Into<String>) -> Self {
Self::new("user", MessageContent::Text(text.into()))
}
pub fn developer(text: impl Into<String>) -> Self {
Self::new("developer", MessageContent::Text(text.into()))
}
pub fn assistant(text: impl Into<String>) -> Self {
Self::new("assistant", MessageContent::Text(text.into()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCallItem {
#[serde(rename = "type")]
pub type_: String,
pub call_id: String,
pub name: String,
pub arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<ItemStatus>,
}
impl FunctionCallItem {
pub fn new(
call_id: impl Into<String>,
name: impl Into<String>,
arguments: impl Into<String>,
) -> Self {
Self {
type_: "function_call".to_string(),
call_id: call_id.into(),
name: name.into(),
arguments: arguments.into(),
id: None,
status: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCallOutputItem {
#[serde(rename = "type")]
pub type_: String,
pub call_id: String,
pub output: FunctionCallOutput,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<ItemStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum FunctionCallOutput {
Text(String),
Parts(Vec<ContentPart>),
}
impl FunctionCallOutputItem {
pub fn new(call_id: impl Into<String>, output: impl Into<String>) -> Self {
Self {
type_: "function_call_output".to_string(),
call_id: call_id.into(),
output: FunctionCallOutput::Text(output.into()),
id: None,
status: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningItem {
#[serde(rename = "type")]
pub type_: String,
pub id: String,
#[serde(default)]
pub summary: Vec<ContentPart>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Vec<ContentPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted_content: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemReference {
#[serde(rename = "type")]
pub type_: String,
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum InputItem {
Message(MessageItem),
FunctionCall(FunctionCallItem),
FunctionCallOutput(FunctionCallOutputItem),
Reasoning(ReasoningItem),
Reference(ItemReference),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionTool {
#[serde(rename = "type")]
pub type_: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
impl FunctionTool {
pub fn new(name: impl Into<String>) -> Self {
Self {
type_: "function".to_string(),
name: name.into(),
description: None,
parameters: None,
strict: None,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_parameters(mut self, parameters: Value) -> Self {
self.parameters = Some(parameters);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Tool {
#[serde(rename = "function")]
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>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpecificFunction {
#[serde(rename = "type")]
pub type_: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(ToolChoiceMode),
Specific(SpecificFunction),
AllowedTools {
#[serde(rename = "type")]
type_: String,
tools: Vec<SpecificFunction>,
#[serde(skip_serializing_if = "Option::is_none")]
mode: Option<ToolChoiceMode>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reasoning {
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<ReasoningSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum TextFormat {
#[serde(rename = "text")]
Text,
#[serde(rename = "json_object")]
JsonObject,
#[serde(rename = "json_schema")]
JsonSchema {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
strict: Option<bool>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<TextFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verbosity: Option<Verbosity>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateResponseBody {
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<Input>,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, 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 presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tool_calls: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<TextConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<ServiceTier>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_cache_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Input {
Text(String),
Items(Vec<InputItem>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UrlCitation {
#[serde(rename = "type")]
pub type_: String,
pub url: String,
pub title: String,
pub start_index: u32,
pub end_index: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogProb {
pub token: String,
pub logprob: f64,
pub bytes: Vec<u8>,
#[serde(default)]
pub top_logprobs: Vec<TopLogProb>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopLogProb {
pub token: String,
pub logprob: f64,
pub bytes: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_tokens_details: Option<InputTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens_details: Option<OutputTokensDetails>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cost: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputTokensDetails {
pub cached_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputTokensDetails {
pub reasoning_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IncompleteDetails {
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum OutputItem {
#[serde(rename = "message")]
Message {
id: String,
status: ItemStatus,
role: MessageRole,
content: Vec<ContentPart>,
#[serde(skip_serializing_if = "Option::is_none")]
phase: Option<String>,
},
#[serde(rename = "function_call")]
FunctionCall {
id: String,
call_id: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
namespace: Option<String>,
arguments: String,
status: ItemStatus,
},
#[serde(rename = "function_call_output")]
FunctionCallOutput {
id: String,
call_id: String,
output: FunctionCallOutput,
status: ItemStatus,
},
#[serde(rename = "reasoning")]
Reasoning {
id: String,
summary: Vec<ContentPart>,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<Vec<ContentPart>>,
#[serde(skip_serializing_if = "Option::is_none")]
encrypted_content: Option<String>,
},
#[serde(rename = "tool_search_call")]
ToolSearchCall {
execution: String,
#[serde(skip_serializing_if = "Option::is_none")]
call_id: Option<String>,
status: ItemStatus,
arguments: Value,
},
#[serde(rename = "tool_search_output")]
ToolSearchOutput {
execution: String,
#[serde(skip_serializing_if = "Option::is_none")]
call_id: Option<String>,
status: ItemStatus,
tools: Vec<Value>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseResource {
pub id: String,
pub object: String,
pub created_at: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub completed_at: Option<i64>,
pub status: ResponseStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub incomplete_details: Option<IncompleteDetails>,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default)]
pub output: Vec<OutputItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<Error>,
#[serde(default)]
pub tools: Vec<Tool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<TextConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Reasoning>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<Usage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tool_calls: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_cache_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Error {
pub code: String,
pub message: String,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorPayload {
#[serde(rename = "type")]
pub type_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub param: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum StreamingEvent {
#[serde(rename = "response.created")]
ResponseCreated {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.queued")]
ResponseQueued {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.in_progress")]
ResponseInProgress {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.completed")]
ResponseCompleted {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.failed")]
ResponseFailed {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.incomplete")]
ResponseIncomplete {
sequence_number: u32,
response: ResponseResource,
},
#[serde(rename = "response.output_item.added")]
OutputItemAdded {
sequence_number: u32,
output_index: u32,
item: Option<OutputItem>,
},
#[serde(rename = "response.output_item.done")]
OutputItemDone {
sequence_number: u32,
output_index: u32,
item: Option<OutputItem>,
},
#[serde(rename = "response.content_part.added")]
ContentPartAdded {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
part: ContentPart,
},
#[serde(rename = "response.content_part.done")]
ContentPartDone {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
part: ContentPart,
},
#[serde(rename = "response.output_text.delta")]
OutputTextDelta {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
delta: String,
#[serde(default)]
logprobs: Vec<LogProb>,
#[serde(skip_serializing_if = "Option::is_none")]
obfuscation: Option<String>,
},
#[serde(rename = "response.output_text.done")]
OutputTextDone {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
text: String,
#[serde(default)]
logprobs: Vec<LogProb>,
},
#[serde(rename = "response.output_text.annotation.added")]
OutputTextAnnotationAdded {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
annotation_index: u32,
annotation: Option<UrlCitation>,
},
#[serde(rename = "response.refusal.delta")]
RefusalDelta {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
delta: String,
},
#[serde(rename = "response.refusal.done")]
RefusalDone {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
refusal: String,
},
#[serde(rename = "response.reasoning.delta")]
ReasoningDelta {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
delta: String,
#[serde(skip_serializing_if = "Option::is_none")]
obfuscation: Option<String>,
},
#[serde(rename = "response.reasoning.done")]
ReasoningDone {
sequence_number: u32,
item_id: String,
output_index: u32,
content_index: u32,
text: String,
},
#[serde(rename = "response.reasoning_text.delta")]
ReasoningTextDelta {
#[serde(default)]
sequence_number: u32,
#[serde(default)]
item_id: String,
#[serde(default)]
output_index: u32,
#[serde(default)]
content_index: u32,
delta: String,
},
#[serde(rename = "response.reasoning_summary_part.added")]
ReasoningSummaryPartAdded {
sequence_number: u32,
item_id: String,
output_index: u32,
summary_index: u32,
part: ContentPart,
},
#[serde(rename = "response.reasoning_summary_part.done")]
ReasoningSummaryPartDone {
sequence_number: u32,
item_id: String,
output_index: u32,
summary_index: u32,
part: ContentPart,
},
#[serde(rename = "response.reasoning_summary_text.delta")]
ReasoningSummaryDelta {
sequence_number: u32,
item_id: String,
output_index: u32,
summary_index: u32,
delta: String,
#[serde(skip_serializing_if = "Option::is_none")]
obfuscation: Option<String>,
},
#[serde(rename = "response.reasoning_summary_text.done")]
ReasoningSummaryDone {
sequence_number: u32,
item_id: String,
output_index: u32,
summary_index: u32,
text: String,
},
#[serde(rename = "response.function_call_arguments.delta")]
FunctionCallArgumentsDelta {
sequence_number: u32,
item_id: String,
output_index: u32,
delta: String,
#[serde(skip_serializing_if = "Option::is_none")]
obfuscation: Option<String>,
},
#[serde(rename = "response.function_call_arguments.done")]
FunctionCallArgumentsDone {
sequence_number: u32,
item_id: String,
output_index: u32,
arguments: String,
},
#[serde(rename = "error")]
Error {
sequence_number: u32,
error: ErrorPayload,
},
}
pub const MAX_TEXT_LENGTH: usize = 10 * 1024 * 1024;
pub const MAX_IMAGE_URL_LENGTH: usize = 20 * 1024 * 1024;
pub const MAX_FILE_DATA_LENGTH: usize = 32 * 1024 * 1024;
pub const MAX_FUNCTION_NAME_LENGTH: usize = 64;
pub const MIN_FUNCTION_NAME_LENGTH: usize = 1;
pub const MAX_METADATA_KEYS: usize = 16;
pub const MAX_METADATA_KEY_LENGTH: usize = 64;
pub const MAX_METADATA_VALUE_LENGTH: usize = 512;
pub const MIN_MAX_OUTPUT_TOKENS: u32 = 16;
pub const MAX_TOP_LOGPROBS: u8 = 20;
pub fn validate_function_name(name: &str) -> bool {
if name.len() < MIN_FUNCTION_NAME_LENGTH || name.len() > MAX_FUNCTION_NAME_LENGTH {
return false;
}
name.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
pub fn validate_metadata(metadata: &HashMap<String, String>) -> bool {
if metadata.len() > MAX_METADATA_KEYS {
return false;
}
metadata
.iter()
.all(|(k, v)| k.len() <= MAX_METADATA_KEY_LENGTH && v.len() <= MAX_METADATA_VALUE_LENGTH)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_item_serialization() {
let msg = MessageItem::user("Hello");
let json = serde_json::to_value(&msg).unwrap();
assert_eq!(json["type"], "message");
assert_eq!(json["role"], "user");
assert_eq!(json["content"], "Hello");
}
#[test]
fn test_function_call_item_serialization() {
let call = FunctionCallItem::new("call_123", "get_weather", r#"{"location":"NYC"}"#);
let json = serde_json::to_value(&call).unwrap();
assert_eq!(json["type"], "function_call");
assert_eq!(json["call_id"], "call_123");
assert_eq!(json["name"], "get_weather");
}
#[test]
fn test_function_call_output_serialization() {
let output = FunctionCallOutputItem::new("call_123", r#"{"temp": 72}"#);
let json = serde_json::to_value(&output).unwrap();
assert_eq!(json["type"], "function_call_output");
assert_eq!(json["call_id"], "call_123");
}
#[test]
fn test_streaming_event_deserialization() {
let json = r#"{"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_123","output_index":0,"content_index":0,"delta":"Hello","logprobs":[]}"#;
let event: StreamingEvent = serde_json::from_str(json).unwrap();
match event {
StreamingEvent::OutputTextDelta { delta, .. } => assert_eq!(delta, "Hello"),
_ => panic!("Wrong event type"),
}
}
#[test]
fn test_hosted_tool_search_response_output_deserialization() {
let json = r#"{
"id": "resp_123",
"object": "response",
"created_at": 1780000000,
"status": "completed",
"model": "gpt-5.5",
"output": [
{
"type": "tool_search_call",
"execution": "server",
"call_id": null,
"status": "completed",
"arguments": { "paths": ["Math"] }
},
{
"type": "tool_search_output",
"execution": "server",
"call_id": null,
"status": "completed",
"tools": [
{
"type": "namespace",
"name": "Math",
"description": "Tools for Math",
"tools": [
{
"type": "function",
"name": "add",
"description": "Add numbers.",
"defer_loading": true,
"parameters": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"],
"additionalProperties": false
}
}
]
}
]
},
{
"type": "function_call",
"id": "fc_123",
"call_id": "call_123",
"name": "add",
"namespace": "Math",
"arguments": "{\"a\":7,\"b\":3}",
"status": "completed"
}
],
"usage": {
"input_tokens": 10,
"output_tokens": 5,
"total_tokens": 15
}
}"#;
let response: ResponseResource = serde_json::from_str(json).unwrap();
assert_eq!(response.id, "resp_123");
assert_eq!(response.output.len(), 3);
assert!(matches!(
response.output[0],
OutputItem::ToolSearchCall { .. }
));
assert!(matches!(
response.output[1],
OutputItem::ToolSearchOutput { .. }
));
match &response.output[2] {
OutputItem::FunctionCall {
namespace, call_id, ..
} => {
assert_eq!(namespace.as_deref(), Some("Math"));
assert_eq!(call_id, "call_123");
}
other => panic!("expected function_call, got {other:?}"),
}
}
#[test]
fn test_reasoning_text_delta_deserialization() {
let json = r#"{"type":"response.reasoning_text.delta","output_index":0,"item_id":"rs_tmp_5jrlmgo85gg","content_index":0,"delta":"User asks","sequence_number":3}"#;
let event: StreamingEvent = serde_json::from_str(json).unwrap();
match event {
StreamingEvent::ReasoningTextDelta { delta, .. } => assert_eq!(delta, "User asks"),
_ => panic!("Wrong event type"),
}
}
#[test]
fn test_error_payload_serialization() {
let error = ErrorPayload {
type_: "invalid_request_error".to_string(),
code: Some("model_not_found".to_string()),
message: "Model not found".to_string(),
param: Some("model".to_string()),
headers: None,
};
let json = serde_json::to_value(&error).unwrap();
assert_eq!(json["type"], "invalid_request_error");
}
#[test]
fn test_validate_function_name() {
assert!(validate_function_name("get_weather"));
assert!(validate_function_name("get-weather"));
assert!(validate_function_name("getWeather123"));
assert!(!validate_function_name("")); assert!(!validate_function_name("a".repeat(65).as_str())); assert!(!validate_function_name("get weather")); assert!(!validate_function_name("get.weather")); }
#[test]
fn test_validate_metadata() {
let mut metadata = HashMap::new();
metadata.insert("key1".to_string(), "value1".to_string());
assert!(validate_metadata(&metadata));
for i in 0..20 {
metadata.insert(format!("key{}", i), "value".to_string());
}
assert!(!validate_metadata(&metadata));
}
#[test]
fn test_content_part_serialization() {
let part = ContentPart::InputText {
text: "Hello".to_string(),
};
let json = serde_json::to_value(&part).unwrap();
assert_eq!(json["type"], "input_text");
assert_eq!(json["text"], "Hello");
}
#[test]
fn test_tool_serialization() {
let tool = Tool::Function {
name: "get_weather".to_string(),
description: Some("Get weather".to_string()),
parameters: Some(serde_json::json!({"type": "object"})),
strict: Some(true),
};
let json = serde_json::to_value(&tool).unwrap();
assert_eq!(json["type"], "function");
assert_eq!(json["name"], "get_weather");
}
#[test]
fn test_usage_parses_openrouter_cost() {
let usage: Usage = serde_json::from_str(
r#"{"input_tokens": 194, "output_tokens": 2, "total_tokens": 196, "cost": 0.00095}"#,
)
.unwrap();
assert_eq!(usage.cost, Some(0.00095));
}
#[test]
fn test_usage_without_cost_is_none() {
let usage: Usage =
serde_json::from_str(r#"{"input_tokens": 10, "output_tokens": 5, "total_tokens": 15}"#)
.unwrap();
assert_eq!(usage.cost, None);
}
}