linger-openai-sdk 0.1.1

Rust-native async SDK for OpenAI APIs with typed requests, streaming, uploads, retries, and pluggable transports.
Documentation
use crate::error::LingerError;
use crate::RequestId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;

/// EN: Request body for `POST /v1/conversations`.
/// 中文:`POST /v1/conversations` 的请求体。
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateConversationRequest {
    /// EN: Optional metadata.
    /// 中文:可选元数据。
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    pub metadata: BTreeMap<String, String>,
    /// EN: Forward-compatible optional fields not yet covered by handwritten types.
    /// 中文:手写类型尚未覆盖的前向兼容可选字段。
    #[serde(flatten)]
    pub extra: BTreeMap<String, Value>,
}

impl CreateConversationRequest {
    /// EN: Starts building a conversation creation request.
    /// 中文:开始构建会话创建请求。
    pub fn builder() -> CreateConversationRequestBuilder {
        CreateConversationRequestBuilder::default()
    }
}

/// EN: Builder for conversation creation requests.
/// 中文:会话创建请求的构建器。
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateConversationRequestBuilder {
    metadata: BTreeMap<String, String>,
    extra: BTreeMap<String, Value>,
}

impl CreateConversationRequestBuilder {
    /// EN: Adds a metadata key/value pair.
    /// 中文:添加一个元数据键值对。
    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }

    /// EN: Adds a forward-compatible JSON field.
    /// 中文:添加前向兼容的 JSON 字段。
    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
        self.extra.insert(name.into(), value);
        self
    }

    /// EN: Builds and validates the request.
    /// 中文:构建并校验请求。
    pub fn build(self) -> Result<CreateConversationRequest, LingerError> {
        validate_metadata(&self.metadata)?;
        validate_extra_fields(&self.extra)?;
        Ok(CreateConversationRequest {
            metadata: self.metadata,
            extra: self.extra,
        })
    }
}

/// EN: Request body for `POST /v1/conversations/{conversation_id}`.
/// 中文:`POST /v1/conversations/{conversation_id}` 的请求体。
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ModifyConversationRequest {
    /// EN: Optional metadata.
    /// 中文:可选元数据。
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    pub metadata: BTreeMap<String, String>,
}

impl ModifyConversationRequest {
    /// EN: Starts building a conversation modification request.
    /// 中文:开始构建会话修改请求。
    pub fn builder() -> ModifyConversationRequestBuilder {
        ModifyConversationRequestBuilder::default()
    }
}

/// EN: Builder for conversation modification requests.
/// 中文:会话修改请求的构建器。
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModifyConversationRequestBuilder {
    metadata: BTreeMap<String, String>,
}

impl ModifyConversationRequestBuilder {
    /// EN: Adds a metadata key/value pair.
    /// 中文:添加一个元数据键值对。
    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }

    /// EN: Builds and validates the request.
    /// 中文:构建并校验请求。
    pub fn build(self) -> Result<ModifyConversationRequest, LingerError> {
        validate_metadata(&self.metadata)?;
        Ok(ModifyConversationRequest {
            metadata: self.metadata,
        })
    }
}

/// EN: Request body for `POST /v1/conversations/{conversation_id}/items`.
/// 中文:`POST /v1/conversations/{conversation_id}/items` 的请求体。
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateConversationItemRequest {
    /// EN: Conversation item payload.
    /// 中文:会话项载荷。
    pub item: Value,
}

impl CreateConversationItemRequest {
    /// EN: Starts building a conversation item creation request.
    /// 中文:开始构建会话项创建请求。
    pub fn builder() -> CreateConversationItemRequestBuilder {
        CreateConversationItemRequestBuilder::default()
    }
}

/// EN: Builder for conversation item creation requests.
/// 中文:会话项创建请求的构建器。
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateConversationItemRequestBuilder {
    item: Option<Value>,
}

impl CreateConversationItemRequestBuilder {
    /// EN: Sets the conversation item payload.
    /// 中文:设置会话项载荷。
    pub fn item(mut self, item: Value) -> Self {
        self.item = Some(item);
        self
    }

    /// EN: Builds and validates the request.
    /// 中文:构建并校验请求。
    pub fn build(self) -> Result<CreateConversationItemRequest, LingerError> {
        let item = self
            .item
            .filter(|value| !value.is_null())
            .ok_or_else(|| LingerError::invalid_config("item is required"))?;
        Ok(CreateConversationItemRequest { item })
    }
}

/// EN: Conversation object returned by the Conversations API.
/// 中文:Conversations API 返回的 Conversation 对象。
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct Conversation {
    /// EN: Conversation id.
    /// 中文:Conversation ID。
    pub id: String,
    /// EN: API object type.
    /// 中文:API 对象类型。
    pub object: String,
    /// EN: Unix timestamp for creation, when returned.
    /// 中文:创建时间的 Unix 时间戳,如响应中存在。
    #[serde(default)]
    pub created_at: Option<u64>,
    /// EN: Metadata returned by the API.
    /// 中文:API 返回的元数据。
    #[serde(default)]
    pub metadata: BTreeMap<String, String>,
    /// EN: Additional fields preserved for forward compatibility.
    /// 中文:为前向兼容保留的额外字段。
    #[serde(flatten)]
    pub extra: BTreeMap<String, Value>,
    /// EN: OpenAI request id from response headers.
    /// 中文:响应头中的 OpenAI 请求 ID。
    #[serde(skip)]
    request_id: Option<RequestId>,
}

impl Conversation {
    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
        self.request_id = request_id;
        self
    }

    /// EN: Returns the OpenAI request id, when present.
    /// 中文:返回 OpenAI 请求 ID,如存在。
    pub fn request_id(&self) -> Option<&RequestId> {
        self.request_id.as_ref()
    }
}

/// EN: Conversation item object returned by the Conversations API.
/// 中文:Conversations API 返回的 Conversation Item 对象。
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ConversationItem {
    /// EN: Conversation item id.
    /// 中文:Conversation Item ID。
    pub id: String,
    /// EN: API object type.
    /// 中文:API 对象类型。
    pub object: String,
    /// EN: Unix timestamp for creation, when returned.
    /// 中文:创建时间的 Unix 时间戳,如响应中存在。
    #[serde(default)]
    pub created_at: Option<u64>,
    /// EN: Additional fields preserved for forward compatibility.
    /// 中文:为前向兼容保留的额外字段。
    #[serde(flatten)]
    pub extra: BTreeMap<String, Value>,
    /// EN: OpenAI request id from response headers.
    /// 中文:响应头中的 OpenAI 请求 ID。
    #[serde(skip)]
    request_id: Option<RequestId>,
}

impl ConversationItem {
    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
        self.request_id = request_id;
        self
    }

    /// EN: Returns the OpenAI request id, when present.
    /// 中文:返回 OpenAI 请求 ID,如存在。
    pub fn request_id(&self) -> Option<&RequestId> {
        self.request_id.as_ref()
    }
}

/// EN: Paginated conversation item list.
/// 中文:分页会话项列表。
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ConversationItemPage {
    /// EN: API list object type.
    /// 中文:API 列表对象类型。
    pub object: String,
    /// EN: Conversation items on this page.
    /// 中文:本页的会话项。
    #[serde(default)]
    pub data: Vec<ConversationItem>,
    /// EN: First item id on this page.
    /// 中文:本页第一个会话项 ID。
    #[serde(default)]
    pub first_id: Option<String>,
    /// EN: Last item id on this page.
    /// 中文:本页最后一个会话项 ID。
    #[serde(default)]
    pub last_id: Option<String>,
    /// EN: Whether more items are available.
    /// 中文:是否还有更多会话项。
    pub has_more: bool,
    /// EN: OpenAI request id from response headers.
    /// 中文:响应头中的 OpenAI 请求 ID。
    #[serde(skip)]
    request_id: Option<RequestId>,
}

impl ConversationItemPage {
    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
        self.request_id = request_id;
        self
    }

    /// EN: Returns the OpenAI request id, when present.
    /// 中文:返回 OpenAI 请求 ID,如存在。
    pub fn request_id(&self) -> Option<&RequestId> {
        self.request_id.as_ref()
    }
}

/// EN: Deletion result returned by the Conversations API.
/// 中文:Conversations API 返回的删除结果。
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversationDeletion {
    /// EN: Deleted conversation id.
    /// 中文:已删除的 Conversation ID。
    pub id: String,
    /// EN: API object type.
    /// 中文:API 对象类型。
    pub object: String,
    /// EN: Whether the conversation was deleted.
    /// 中文:Conversation 是否已删除。
    pub deleted: bool,
    /// EN: OpenAI request id from response headers.
    /// 中文:响应头中的 OpenAI 请求 ID。
    #[serde(skip)]
    request_id: Option<RequestId>,
}

impl ConversationDeletion {
    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
        self.request_id = request_id;
        self
    }

    /// EN: Returns the OpenAI request id, when present.
    /// 中文:返回 OpenAI 请求 ID,如存在。
    pub fn request_id(&self) -> Option<&RequestId> {
        self.request_id.as_ref()
    }
}

/// EN: Deletion result returned by the Conversation Items API.
/// 中文:Conversation Items API 返回的删除结果。
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversationItemDeletion {
    /// EN: Deleted item id.
    /// 中文:已删除的会话项 ID。
    pub id: String,
    /// EN: API object type.
    /// 中文:API 对象类型。
    pub object: String,
    /// EN: Whether the item was deleted.
    /// 中文:会话项是否已删除。
    pub deleted: bool,
    /// EN: OpenAI request id from response headers.
    /// 中文:响应头中的 OpenAI 请求 ID。
    #[serde(skip)]
    request_id: Option<RequestId>,
}

impl ConversationItemDeletion {
    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
        self.request_id = request_id;
        self
    }

    /// EN: Returns the OpenAI request id, when present.
    /// 中文:返回 OpenAI 请求 ID,如存在。
    pub fn request_id(&self) -> Option<&RequestId> {
        self.request_id.as_ref()
    }
}

fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
    for key in metadata.keys() {
        if key.trim().is_empty() {
            return Err(LingerError::invalid_config(
                "metadata keys must not be empty",
            ));
        }
    }
    Ok(())
}

fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
    for (key, value) in extra {
        if key.trim().is_empty() {
            return Err(LingerError::invalid_config(
                "extra field names must not be empty",
            ));
        }
        if value.is_null() {
            return Err(LingerError::invalid_config(format!(
                "extra field {key} must not be null"
            )));
        }
    }
    Ok(())
}