use serde::Deserialize;
use serde_json::json;
#[derive(Debug, Clone)]
pub enum BuiltinTool {
WebSearch,
FileSearch { vector_store_ids: Vec<String> },
CodeInterpreter,
ComputerUse {
display_width: Option<u32>,
display_height: Option<u32>,
},
}
impl BuiltinTool {
pub(crate) fn to_api_value(&self) -> serde_json::Value {
match self {
BuiltinTool::WebSearch => json!({"type": "web_search"}),
BuiltinTool::FileSearch { vector_store_ids } => json!({
"type": "file_search",
"vector_store_ids": vector_store_ids,
}),
BuiltinTool::CodeInterpreter => json!({"type": "code_interpreter"}),
BuiltinTool::ComputerUse {
display_width,
display_height,
} => {
let mut val = json!({"type": "computer_use"});
if let Some(w) = display_width {
val["display_width"] = json!(w);
}
if let Some(h) = display_height {
val["display_height"] = json!(h);
}
val
}
}
}
}
#[derive(Debug, Clone)]
pub struct ResponsesConfig {
pub api_key: String,
pub model: String,
pub base_url: String,
pub temperature: Option<f32>,
pub max_tokens: Option<usize>,
pub top_p: Option<f32>,
pub builtin_tools: Vec<BuiltinTool>,
}
impl Default for ResponsesConfig {
fn default() -> Self {
Self {
api_key: String::new(),
base_url: "https://api.openai.com/v1".to_string(),
model: "gpt-4o".to_string(),
temperature: None,
max_tokens: None,
top_p: None,
builtin_tools: Vec::new(),
}
}
}
impl ResponsesConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
..Default::default()
}
}
pub fn from_env() -> Result<Self, ResponsesError> {
let api_key = std::env::var("OPENAI_API_KEY").map_err(|_| {
ResponsesError::Api("OPENAI_API_KEY environment variable must be set".to_string())
})?;
let base_url = std::env::var("OPENAI_BASE_URL")
.unwrap_or_else(|_| "https://api.openai.com/v1".to_string());
let model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-4o".to_string());
Ok(Self {
api_key,
base_url,
model,
..Default::default()
})
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_temperature(mut self, temp: f32) -> Self {
self.temperature = Some(temp);
self
}
pub fn with_max_tokens(mut self, max: usize) -> Self {
self.max_tokens = Some(max);
self
}
pub fn with_builtin_tool(mut self, tool: BuiltinTool) -> Self {
self.builtin_tools.push(tool);
self
}
}
#[derive(Debug)]
pub enum ResponsesError {
Http(String),
Api(String),
Parse(String),
}
impl std::fmt::Display for ResponsesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResponsesError::Http(msg) => write!(f, "HTTP error: {}", msg),
ResponsesError::Api(msg) => write!(f, "API error: {}", msg),
ResponsesError::Parse(msg) => write!(f, "Parse error: {}", msg),
}
}
}
impl std::error::Error for ResponsesError {}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct ResponsesApiResponse {
pub id: String,
pub object: Option<String>,
pub model: Option<String>,
pub output: Vec<ResponsesOutputItem>,
pub usage: Option<ResponsesUsage>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub(crate) enum ResponsesOutputItem {
#[serde(rename = "message")]
Message(ResponsesMessage),
#[serde(rename = "web_search_call")]
WebSearchCall(ResponsesWebSearchCall),
#[serde(rename = "file_search_call")]
FileSearchCall(ResponsesFileSearchCall),
#[serde(rename = "code_interpreter_call")]
CodeInterpreterCall(ResponsesCodeInterpreterCall),
#[serde(rename = "computer_call")]
ComputerCall(ResponsesComputerCall),
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct ResponsesMessage {
pub id: Option<String>,
pub role: Option<String>,
pub content: Vec<ResponsesContentPart>,
pub status: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub(crate) enum ResponsesContentPart {
#[serde(rename = "output_text")]
OutputText(ResponsesOutputText),
#[serde(rename = "refusal")]
Refusal(ResponsesRefusal),
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct ResponsesOutputText {
pub text: String,
#[serde(default)]
pub annotations: Vec<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesRefusal {
pub refusal: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesWebSearchCall {
pub id: String,
pub status: String,
pub query: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesFileSearchCall {
pub id: String,
pub status: String,
pub query: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesCodeInterpreterCall {
pub id: String,
pub code: Option<String>,
pub results: Option<Vec<serde_json::Value>>,
pub status: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesComputerCall {
pub id: String,
pub action: Option<serde_json::Value>,
pub status: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesUsage {
pub input_tokens: usize,
pub output_tokens: usize,
pub total_tokens: usize,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
#[serde(tag = "type")]
pub(crate) enum ResponsesStreamEvent {
#[serde(rename = "response.created")]
Created(serde_json::Value),
#[serde(rename = "response.output_item.added")]
OutputItemAdded(serde_json::Value),
#[serde(rename = "response.content_part.added")]
ContentPartAdded(serde_json::Value),
#[serde(rename = "response.output_text.delta")]
OutputTextDelta(ResponsesTextDelta),
#[serde(rename = "response.output_text.done")]
OutputTextDone(serde_json::Value),
#[serde(rename = "response.content_part.done")]
ContentPartDone(serde_json::Value),
#[serde(rename = "response.output_item.done")]
OutputItemDone(serde_json::Value),
#[serde(rename = "response.web_search_call.in_progress")]
WebSearchInProgress(serde_json::Value),
#[serde(rename = "response.web_search_call.searching")]
WebSearchSearching(serde_json::Value),
#[serde(rename = "response.web_search_call.completed")]
WebSearchCompleted(serde_json::Value),
#[serde(rename = "response.code_interpreter_call.in_progress")]
CodeInterpreterInProgress(serde_json::Value),
#[serde(rename = "response.code_interpreter_call.code_delta")]
CodeInterpreterCodeDelta(serde_json::Value),
#[serde(rename = "response.code_interpreter_call.completed")]
CodeInterpreterCompleted(serde_json::Value),
#[serde(rename = "response.file_search_call.in_progress")]
FileSearchInProgress(serde_json::Value),
#[serde(rename = "response.file_search_call.completed")]
FileSearchCompleted(serde_json::Value),
#[serde(rename = "response.completed")]
Completed(ResponsesCompletedEvent),
#[serde(rename = "response.failed")]
Failed(serde_json::Value),
}
#[derive(Debug, Deserialize)]
pub(crate) struct ResponsesTextDelta {
pub delta: String,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct ResponsesCompletedEvent {
pub response: ResponsesApiResponse,
}