use adk_core::{AdkError, ErrorCategory, ErrorComponent};
pub struct ConversationsClient {
http: reqwest::Client,
api_key: String,
base_url: String,
}
impl ConversationsClient {
pub fn new(api_key: impl Into<String>, base_url: Option<String>) -> Self {
Self {
http: reqwest::Client::new(),
api_key: api_key.into(),
base_url: base_url.unwrap_or_else(|| "https://api.openai.com/v1".to_string()),
}
}
pub async fn create(&self) -> Result<String, AdkError> {
let url = format!("{}/conversations", self.base_url);
let response = self
.http
.post(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.body("{}")
.send()
.await
.map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Unavailable,
"model.openai_responses.request",
format!("OpenAI Conversations API network error during create: {e}"),
)
.with_provider("openai-responses")
})?;
let status = response.status();
if !status.is_success() {
let error_body = response.text().await.unwrap_or_default();
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.conversation_create_failed",
format!(
"OpenAI Conversations API create failed with status {status}: {error_body}"
),
)
.with_provider("openai-responses")
.with_upstream_status(status.as_u16()));
}
let body = response.text().await.map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.parse",
format!("Failed to read conversation create response body: {e}"),
)
.with_provider("openai-responses")
})?;
let json: serde_json::Value = serde_json::from_str(&body).map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.parse",
format!("Failed to parse conversation create response JSON: {e}"),
)
.with_provider("openai-responses")
})?;
let id = json
.get("id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.parse",
"Conversation create response missing 'id' field",
)
.with_provider("openai-responses")
})?
.to_string();
Ok(id)
}
pub async fn get(&self, conversation_id: &str) -> Result<serde_json::Value, AdkError> {
let url = format!("{}/conversations/{conversation_id}", self.base_url);
let response = self
.http
.get(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await
.map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Unavailable,
"model.openai_responses.request",
format!("OpenAI Conversations API network error during get: {e}"),
)
.with_provider("openai-responses")
})?;
let status = response.status();
if status == reqwest::StatusCode::NOT_FOUND {
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::NotFound,
"model.openai_responses.conversation_not_found",
format!("Conversation '{conversation_id}' not found"),
)
.with_provider("openai-responses")
.with_upstream_status(404));
}
if !status.is_success() {
let error_body = response.text().await.unwrap_or_default();
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.conversation_get_failed",
format!("OpenAI Conversations API get failed with status {status}: {error_body}"),
)
.with_provider("openai-responses")
.with_upstream_status(status.as_u16()));
}
let body = response.text().await.map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.parse",
format!("Failed to read conversation get response body: {e}"),
)
.with_provider("openai-responses")
})?;
let json: serde_json::Value = serde_json::from_str(&body).map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.parse",
format!("Failed to parse conversation get response JSON: {e}"),
)
.with_provider("openai-responses")
})?;
Ok(json)
}
pub async fn delete(&self, conversation_id: &str) -> Result<(), AdkError> {
let url = format!("{}/conversations/{conversation_id}", self.base_url);
let response = self
.http
.delete(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await
.map_err(|e| {
AdkError::new(
ErrorComponent::Model,
ErrorCategory::Unavailable,
"model.openai_responses.request",
format!("OpenAI Conversations API network error during delete: {e}"),
)
.with_provider("openai-responses")
})?;
let status = response.status();
if status == reqwest::StatusCode::NOT_FOUND {
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::NotFound,
"model.openai_responses.conversation_not_found",
format!("Conversation '{conversation_id}' not found"),
)
.with_provider("openai-responses")
.with_upstream_status(404));
}
if !status.is_success() {
let error_body = response.text().await.unwrap_or_default();
return Err(AdkError::new(
ErrorComponent::Model,
ErrorCategory::Internal,
"model.openai_responses.conversation_delete_failed",
format!(
"OpenAI Conversations API delete failed with status {status}: {error_body}"
),
)
.with_provider("openai-responses")
.with_upstream_status(status.as_u16()));
}
Ok(())
}
}