use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{MessageRole, OutputItem};
use crate::llm::provider::ToolDefinition;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Request {
pub model: String,
pub input: Vec<OutputItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDefinition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(default)]
pub stream: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<TruncationConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tool_calls: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logit_bias: Option<hashbrown::HashMap<String, f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: 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 previous_response_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReasoningConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TruncationConfig {
pub strategy: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_prompt_tokens: Option<u64>,
}
impl Request {
pub fn new(model: impl Into<String>, input: Vec<OutputItem>) -> Self {
Self {
model: model.into(),
input,
tools: None,
tool_choice: None,
stream: false,
temperature: None,
top_p: None,
truncation: None,
max_output_tokens: None,
max_tool_calls: None,
stop: None,
presence_penalty: None,
frequency_penalty: None,
logit_bias: None,
logprobs: None,
top_logprobs: None,
user: None,
service_tier: None,
reasoning: None,
store: None,
previous_response_id: None,
include: None,
metadata: None,
}
}
pub fn from_message(model: impl Into<String>, text: impl Into<String>) -> Self {
let item = OutputItem::completed_message(
"msg_init",
MessageRole::User,
vec![super::ContentPart::input_text(text)],
);
Self::new(model, vec![item])
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(ToolChoiceMode),
Tool(SpecificToolChoice),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoiceMode {
Auto,
None,
Required,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SpecificToolChoice {
#[serde(rename = "type")]
pub tool_type: String,
pub function: FunctionName,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionName {
pub name: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_serialization() {
let req = Request::from_message("gpt-5", "Hello");
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"model\":\"gpt-5\""));
assert!(json.contains("\"input\":["));
assert!(json.contains("\"type\":\"message\""));
}
}