diff --git a/async-openai/README.md b/async-openai/README.md index e723e3aa..823df5d3 100644 --- a/async-openai/README.md +++ b/async-openai/README.md @@ -19,32 +19,23 @@ ## Overview -`async-openai` is an unofficial Rust library for OpenAI. - -- It's based on [OpenAI OpenAPI spec](https://github.com/openai/openai-openapi) -- Current features: - - [x] Administration (partially implemented) - - [x] Assistants (beta) - - [x] Audio - - [x] Batch - - [x] Chat - - [x] ChatKit (beta) - - [x] Completions (legacy) - - [x] Conversations - - [x] Containers - - [x] Embeddings - - [x] Evals - - [x] Files - - [x] Fine-Tuning - - [x] Images - - [x] Models - - [x] Moderations - - [x] Realtime (partially implemented) - - [x] Responses - - [x] Uploads - - [x] Vector Stores - - [x] Videos - - [x] Webhooks +`async-openai` is an unofficial Rust library for OpenAI, based on [OpenAI OpenAPI spec](https://github.com/openai/openai-openapi). It implements all APIs from the spec: + +| Features | APIs | +|---|---| +| **Responses API** | Responses, Conversations, Streaming events | +| **Webhooks** | Webhook Events | +| **Platform APIs** | Audio, Audio Streaming, Videos, Images, Image Streaming, Embeddings, Evals, Fine-tuning, Graders, Batch, Files, Uploads, Models, Moderations | +| **Vector stores** | Vector stores, Vector store files, Vector store file batches | +| **ChatKit** (Beta) | ChatKit | +| **Containers** | Containers, Container Files | +| **Realtime** | Realtime Calls, Client secrets, Client events, Server events | +| **Chat Completions** | Chat Completions, Streaming | +| **Assistants** (Beta) | Assistants, Threads, Messages, Runs, Run steps, Streaming | +| **Administration** | Administration, Admin API Keys, Invites, Users, Projects, Project users, Project service accounts, Project API keys, Project rate limits, Audit logs, Usage, Certificates | +| **Legacy** | Completions | + +Features that makes `async-openai` unique: - Bring your own custom types for Request or Response objects. - SSE streaming on available APIs - Requests (except SSE streaming) including form submissions are retried with exponential backoff when [rate limited](https://platform.openai.com/docs/guides/rate-limits). @@ -68,9 +59,9 @@ $Env:OPENAI_API_KEY='sk-...' - Visit [examples](https://github.com/64bit/async-openai/tree/main/examples) directory on how to use `async-openai`. - Visit [docs.rs/async-openai](https://docs.rs/async-openai) for docs. -## Realtime API +## Realtime -Only types for Realtime API are implemented, and can be enabled with feature flag `realtime`. +Realtime types and APIs can be enabled with feature flag `realtime`. ## Webhooks @@ -122,7 +113,11 @@ async fn main() -> Result<(), Box> { ## Bring Your Own Types -Enable methods whose input and outputs are generics with `byot` feature. It creates a new method with same name and `_byot` suffix. +Enable methods whose input and outputs are generics with `byot` feature. It creates a new method with same name and `_byot` suffix. + +`byot` requires trait bounds: +- a request type (`fn` input parameter) needs to implement `serde::Serialize` or `std::fmt::Display` trait +- a response type (`fn` ouput parameter) needs to implement `serde::de::DeserializeOwned` trait. For example, to use `serde_json::Value` as request and response type: ```rust @@ -147,27 +142,35 @@ let response: Value = client This can be useful in many scenarios: - To use this library with other OpenAI compatible APIs whose types don't exactly match OpenAI. -- Extend existing types in this crate with new fields with `serde`. +- Extend existing types in this crate with new fields with `serde` (for example with `#[serde(flatten)]`). - To avoid verbose types. - To escape deserialization errors. Visit [examples/bring-your-own-type](https://github.com/64bit/async-openai/tree/main/examples/bring-your-own-type) directory to learn more. -## Dynamic Dispatch for Different Providers +## Dynamic Dispatch for OpenAI-compatible Providers + +This allows you to use same code (say a `fn`) to call APIs on different OpenAI-compatible providers. -For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config` -trait object, then your client can accept any wrapped configuration type. +For any struct that implements `Config` trait, wrap it in a smart pointer and cast the pointer to `dyn Config` +trait object, then create a client with `Box` or `Arc` wrapped configuration. -For example, +For example: ```rust -use async_openai::{Client, config::Config, config::OpenAIConfig}; +use async_openai::{Client, config::{Config, OpenAIConfig}}; + +// Use `Box` or `std::sync::Arc` to wrap the config +let config = Box::new(OpenAIConfig::default()) as Box; +// create client +let client: Client> = Client::with_config(config); -let openai_config = OpenAIConfig::default(); -// You can use `std::sync::Arc` to wrap the config as well -let config = Box::new(openai_config) as Box; -let client: Client > = Client::with_config(config); +// A function can now accept a `&Client>` parameter +// which can invoke any openai compatible api +fn chat_completion(client: &Client>) { + todo!() +} ``` ## Contributing @@ -187,8 +190,11 @@ To maintain quality of the project, a minimum of the following is a must for cod This project adheres to [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in async-openai by you, shall be licensed as MIT, without any additional terms or conditions. + ## Complimentary Crates - [async-openai-wasm](https://github.com/ifsheldon/async-openai-wasm) provides WASM support. +- [openai-func-enums](https://github.com/frankfralick/openai-func-enums) macros for working with function/tool calls. ## License diff --git a/async-openai/src/admin.rs b/async-openai/src/admin.rs new file mode 100644 index 00000000..5372775c --- /dev/null +++ b/async-openai/src/admin.rs @@ -0,0 +1,46 @@ +use crate::{ + admin_api_keys::AdminAPIKeys, audit_logs::AuditLogs, certificates::Certificates, + config::Config, invites::Invites, projects::Projects, users::Users, Client, +}; + +/// Admin group for all administration APIs. +/// This groups together admin API keys, invites, users, projects, audit logs, and certificates. +pub struct Admin<'c, C: Config> { + client: &'c Client, +} + +impl<'c, C: Config> Admin<'c, C> { + pub(crate) fn new(client: &'c Client) -> Self { + Self { client } + } + + /// To call [AdminAPIKeys] group related APIs using this client. + pub fn api_keys(&self) -> AdminAPIKeys<'_, C> { + AdminAPIKeys::new(self.client) + } + + /// To call [Invites] group related APIs using this client. + pub fn invites(&self) -> Invites<'_, C> { + Invites::new(self.client) + } + + /// To call [Users] group related APIs using this client. + pub fn users(&self) -> Users<'_, C> { + Users::new(self.client) + } + + /// To call [Projects] group related APIs using this client. + pub fn projects(&self) -> Projects<'_, C> { + Projects::new(self.client) + } + + /// To call [AuditLogs] group related APIs using this client. + pub fn audit_logs(&self) -> AuditLogs<'_, C> { + AuditLogs::new(self.client) + } + + /// To call [Certificates] group related APIs using this client. + pub fn certificates(&self) -> Certificates<'_, C> { + Certificates::new(self.client) + } +} diff --git a/async-openai/src/admin_api_keys.rs b/async-openai/src/admin_api_keys.rs new file mode 100644 index 00000000..20b58a53 --- /dev/null +++ b/async-openai/src/admin_api_keys.rs @@ -0,0 +1,60 @@ +use serde::Serialize; + +use crate::{ + config::Config, + error::OpenAIError, + types::admin::api_keys::{ + AdminApiKey, AdminApiKeyDeleteResponse, ApiKeyList, CreateAdminApiKeyRequest, + }, + Client, +}; + +/// Admin API keys enable Organization Owners to programmatically manage various aspects of their +/// organization, including users, projects, and API keys. These keys provide administrative capabilities, +/// allowing you to automate organization management tasks. +pub struct AdminAPIKeys<'c, C: Config> { + client: &'c Client, +} + +impl<'c, C: Config> AdminAPIKeys<'c, C> { + pub fn new(client: &'c Client) -> Self { + Self { client } + } + + /// List all organization and project API keys. + #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)] + pub async fn list(&self, query: &Q) -> Result + where + Q: Serialize + ?Sized, + { + self.client + .get_with_query("/organization/admin_api_keys", &query) + .await + } + + /// Create an organization admin API key. + pub async fn create( + &self, + request: CreateAdminApiKeyRequest, + ) -> Result { + self.client + .post("/organization/admin_api_keys", request) + .await + } + + /// Retrieve a single organization API key. + #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)] + pub async fn retrieve(&self, key_id: &str) -> Result { + self.client + .get(format!("/organization/admin_api_keys/{key_id}").as_str()) + .await + } + + /// Delete an organization admin API key. + #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)] + pub async fn delete(&self, key_id: &str) -> Result { + self.client + .delete(format!("/organization/admin_api_keys/{key_id}").as_str()) + .await + } +} diff --git a/async-openai/src/assistants.rs b/async-openai/src/assistants.rs index 494f2dec..abca6d75 100644 --- a/async-openai/src/assistants.rs +++ b/async-openai/src/assistants.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ + types::assistants::{ AssistantObject, CreateAssistantRequest, DeleteAssistantResponse, ListAssistantsResponse, ModifyAssistantRequest, }, diff --git a/async-openai/src/audit_logs.rs b/async-openai/src/audit_logs.rs index 753c318b..7f09f9df 100644 --- a/async-openai/src/audit_logs.rs +++ b/async-openai/src/audit_logs.rs @@ -1,6 +1,8 @@ use serde::Serialize; -use crate::{config::Config, error::OpenAIError, types::ListAuditLogsResponse, Client}; +use crate::{ + config::Config, error::OpenAIError, types::admin::audit_logs::ListAuditLogsResponse, Client, +}; /// Logs of user actions and configuration changes within this organization. /// To log events, you must activate logging in the [Organization Settings](https://platform.openai.com/settings/organization/general). diff --git a/async-openai/src/certificates.rs b/async-openai/src/certificates.rs new file mode 100644 index 00000000..44048fc5 --- /dev/null +++ b/async-openai/src/certificates.rs @@ -0,0 +1,124 @@ +use serde::Serialize; + +use crate::{ + config::Config, + error::OpenAIError, + types::admin::certificates::{ + Certificate, DeleteCertificateResponse, ListCertificatesResponse, ModifyCertificateRequest, + ToggleCertificatesRequest, UploadCertificateRequest, + }, + Client, +}; + +/// Certificates enable Mutual TLS (mTLS) authentication for your organization. +/// Manage certificates at the organization level. +pub struct Certificates<'c, C: Config> { + client: &'c Client, +} + +impl<'c, C: Config> Certificates<'c, C> { + pub fn new(client: &'c Client) -> Self { + Self { client } + } + + // Organization-level certificate operations + + /// List all certificates for the organization. + #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)] + pub async fn list_organization( + &self, + query: &Q, + ) -> Result + where + Q: Serialize + ?Sized, + { + self.client + .get_with_query("/organization/certificates", &query) + .await + } + + /// Upload a certificate to the organization. + /// This does not automatically activate the certificate. + pub async fn upload_organization( + &self, + request: UploadCertificateRequest, + ) -> Result { + self.client + .post("/organization/certificates", request) + .await + } + + /// Activate certificates for the organization. + /// You can atomically and idempotently activate up to 10 certificates at a time. + pub async fn activate_organization( + &self, + request: ToggleCertificatesRequest, + ) -> Result { + self.client + .post("/organization/certificates/activate", request) + .await + } + + /// Deactivate certificates for the organization. + /// You can atomically and idempotently deactivate up to 10 certificates at a time. + pub async fn deactivate_organization( + &self, + request: ToggleCertificatesRequest, + ) -> Result { + self.client + .post("/organization/certificates/deactivate", request) + .await + } + + /// Retrieve a single certificate. + #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)] + pub async fn retrieve(&self, certificate_id: &str) -> Result { + self.client + .get(format!("/organization/certificates/{certificate_id}").as_str()) + .await + } + + /// Retrieve a single certificate with optional include parameters. + pub async fn retrieve_with_query( + &self, + certificate_id: &str, + query: &Q, + ) -> Result + where + Q: Serialize + ?Sized, + { + self.client + .get_with_query( + format!("/organization/certificates/{certificate_id}").as_str(), + query, + ) + .await + } + + /// Modify a certificate. Note that only the name can be modified. + #[crate::byot(T0 = std::fmt::Display, T1 = serde::Serialize, R = serde::de::DeserializeOwned)] + pub async fn modify( + &self, + certificate_id: &str, + request: ModifyCertificateRequest, + ) -> Result { + self.client + .post( + format!("/organization/certificates/{certificate_id}").as_str(), + request, + ) + .await + } + + /// Delete a certificate from the organization. + /// The certificate must be inactive for the organization and all projects. + #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)] + pub async fn delete( + &self, + certificate_id: &str, + ) -> Result { + self.client + .delete(format!("/organization/certificates/{certificate_id}").as_str()) + .await + } +} diff --git a/async-openai/src/client.rs b/async-openai/src/client.rs index b78c72fb..ec8363dd 100644 --- a/async-openai/src/client.rs +++ b/async-openai/src/client.rs @@ -2,11 +2,12 @@ use std::pin::Pin; use bytes::Bytes; use futures::{stream::StreamExt, Stream}; -use reqwest::{multipart::Form, Response}; +use reqwest::{header::HeaderMap, multipart::Form, Response}; use reqwest_eventsource::{Error as EventSourceError, Event, EventSource, RequestBuilderExt}; use serde::{de::DeserializeOwned, Serialize}; use crate::{ + admin::Admin, chatkit::Chatkit, config::{Config, OpenAIConfig}, error::{map_deserialization_error, ApiError, OpenAIError, StreamError, WrappedError}, @@ -14,11 +15,13 @@ use crate::{ image::Images, moderation::Moderations, traits::AsyncTryFrom, - Assistants, Audio, AuditLogs, Batches, Chat, Completions, Containers, Conversations, - Embeddings, Evals, FineTuning, Invites, Models, Projects, Responses, Threads, Uploads, Users, - VectorStores, Videos, + Assistants, Audio, Batches, Chat, Completions, Containers, Conversations, Embeddings, Evals, + FineTuning, Models, Responses, Threads, Uploads, Usage, VectorStores, Videos, }; +#[cfg(feature = "realtime")] +use crate::Realtime; + #[derive(Debug, Clone, Default)] /// Client is a container for config, backoff and http_client /// used to make API calls. @@ -149,24 +152,15 @@ impl Client { Batches::new(self) } - /// To call [AuditLogs] group related APIs using this client. - pub fn audit_logs(&self) -> AuditLogs<'_, C> { - AuditLogs::new(self) - } - - /// To call [Invites] group related APIs using this client. - pub fn invites(&self) -> Invites<'_, C> { - Invites::new(self) + /// To call [Admin] group related APIs using this client. + /// This groups together admin API keys, invites, users, projects, audit logs, and certificates. + pub fn admin(&self) -> Admin<'_, C> { + Admin::new(self) } - /// To call [Users] group related APIs using this client. - pub fn users(&self) -> Users<'_, C> { - Users::new(self) - } - - /// To call [Projects] group related APIs using this client. - pub fn projects(&self) -> Projects<'_, C> { - Projects::new(self) + /// To call [Usage] group related APIs using this client. + pub fn usage(&self) -> Usage<'_, C> { + Usage::new(self) } /// To call [Responses] group related APIs using this client. @@ -193,6 +187,12 @@ impl Client { Chatkit::new(self) } + #[cfg(feature = "realtime")] + /// To call [Realtime] group related APIs using this client. + pub fn realtime(&self) -> Realtime<'_, C> { + Realtime::new(self) + } + pub fn config(&self) -> &C { &self.config } @@ -251,7 +251,7 @@ impl Client { } /// Make a GET request to {path} and return the response body - pub(crate) async fn get_raw(&self, path: &str) -> Result { + pub(crate) async fn get_raw(&self, path: &str) -> Result<(Bytes, HeaderMap), OpenAIError> { let request_maker = || async { Ok(self .http_client @@ -268,7 +268,7 @@ impl Client { &self, path: &str, query: &Q, - ) -> Result + ) -> Result<(Bytes, HeaderMap), OpenAIError> where Q: Serialize + ?Sized, { @@ -286,7 +286,11 @@ impl Client { } /// Make a POST request to {path} and return the response body - pub(crate) async fn post_raw(&self, path: &str, request: I) -> Result + pub(crate) async fn post_raw( + &self, + path: &str, + request: I, + ) -> Result<(Bytes, HeaderMap), OpenAIError> where I: Serialize, { @@ -323,7 +327,11 @@ impl Client { } /// POST a form at {path} and return the response body - pub(crate) async fn post_form_raw(&self, path: &str, form: F) -> Result + pub(crate) async fn post_form_raw( + &self, + path: &str, + form: F, + ) -> Result<(Bytes, HeaderMap), OpenAIError> where Form: AsyncTryFrom, F: Clone, @@ -439,7 +447,7 @@ impl Client { /// request_maker serves one purpose: to be able to create request again /// to retry API call after getting rate limited. request_maker is async because /// reqwest::multipart::Form is created by async calls to read files for uploads. - async fn execute_raw(&self, request_maker: M) -> Result + async fn execute_raw(&self, request_maker: M) -> Result<(Bytes, HeaderMap), OpenAIError> where M: Fn() -> Fut, Fut: core::future::Future>, @@ -457,7 +465,7 @@ impl Client { let status = response.status(); match read_response(response).await { - Ok(bytes) => Ok(bytes), + Ok((bytes, headers)) => Ok((bytes, headers)), Err(e) => { match e { OpenAIError::ApiError(api_error) => { @@ -498,7 +506,7 @@ impl Client { M: Fn() -> Fut, Fut: core::future::Future>, { - let bytes = self.execute_raw(request_maker).await?; + let (bytes, _headers) = self.execute_raw(request_maker).await?; let response: O = serde_json::from_slice(bytes.as_ref()) .map_err(|e| map_deserialization_error(e, bytes.as_ref()))?; @@ -573,8 +581,9 @@ impl Client { } } -async fn read_response(response: Response) -> Result { +async fn read_response(response: Response) -> Result<(Bytes, HeaderMap), OpenAIError> { let status = response.status(); + let headers = response.headers().clone(); let bytes = response.bytes().await.map_err(OpenAIError::Reqwest)?; if status.is_server_error() { @@ -597,7 +606,7 @@ async fn read_response(response: Response) -> Result { return Err(OpenAIError::ApiError(wrapped_error.error)); } - Ok(bytes) + Ok((bytes, headers)) } async fn map_stream_error(value: EventSourceError) -> OpenAIError { diff --git a/async-openai/src/config.rs b/async-openai/src/config.rs index d694c891..b0703b53 100644 --- a/async-openai/src/config.rs +++ b/async-openai/src/config.rs @@ -70,7 +70,13 @@ impl Default for OpenAIConfig { Self { api_base: OPENAI_API_BASE.to_string(), api_key: std::env::var("OPENAI_API_KEY") - .unwrap_or_else(|_| "".to_string()) + .or_else(|_| { + std::env::var("OPENAI_ADMIN_KEY").map(|admin_key| { + tracing::warn!("Using OPENAI_ADMIN_KEY, OPENAI_API_KEY not set"); + admin_key + }) + }) + .unwrap_or_default() .into(), org_id: Default::default(), project_id: Default::default(), diff --git a/async-openai/src/container_files.rs b/async-openai/src/container_files.rs index 633f873c..fd0f03d3 100644 --- a/async-openai/src/container_files.rs +++ b/async-openai/src/container_files.rs @@ -69,8 +69,10 @@ impl<'c, C: Config> ContainerFiles<'c, C> { /// Returns the content of a container file. pub async fn content(&self, file_id: &str) -> Result { - self.client + let (bytes, _headers) = self + .client .get_raw(format!("/containers/{}/files/{file_id}/content", self.container_id).as_str()) - .await + .await?; + Ok(bytes) } } diff --git a/async-openai/src/file.rs b/async-openai/src/file.rs index 057967b6..bce20415 100644 --- a/async-openai/src/file.rs +++ b/async-openai/src/file.rs @@ -61,9 +61,11 @@ impl<'c, C: Config> Files<'c, C> { /// Returns the contents of the specified file pub async fn content(&self, file_id: &str) -> Result { - self.client + let (bytes, _headers) = self + .client .get_raw(format!("/files/{file_id}/content").as_str()) - .await + .await?; + Ok(bytes) } } diff --git a/async-openai/src/invites.rs b/async-openai/src/invites.rs index 83600176..3291b3b9 100644 --- a/async-openai/src/invites.rs +++ b/async-openai/src/invites.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{Invite, InviteDeleteResponse, InviteListResponse, InviteRequest}, + types::admin::invites::{Invite, InviteDeleteResponse, InviteListResponse, InviteRequest}, Client, }; diff --git a/async-openai/src/lib.rs b/async-openai/src/lib.rs index f570ba63..846eff32 100644 --- a/async-openai/src/lib.rs +++ b/async-openai/src/lib.rs @@ -94,20 +94,20 @@ //! # }); //!``` //! -//! ## Dynamic Dispatch for Different Providers +//! ## Dynamic Dispatch for OpenAI-compatible Providers //! -//! For any struct that implements `Config` trait, you can wrap it in a smart pointer and cast the pointer to `dyn Config` -//! trait object, then your client can accept any wrapped configuration type. +//! For any struct that implements `Config` trait, wrap it in a smart pointer and cast the pointer to `dyn Config` +//! trait object, then create a client with `Box` or `Arc` wrapped configuration. //! -//! For example, +//! For example: //! ``` -//! use async_openai::{Client, config::Config, config::OpenAIConfig}; -//! unsafe { std::env::set_var("OPENAI_API_KEY", "only for doc test") } +//! use async_openai::{Client, config::{Config, OpenAIConfig}}; //! -//! let openai_config = OpenAIConfig::default(); -//! // You can use `std::sync::Arc` to wrap the config as well -//! let config = Box::new(openai_config) as Box; -//! let client: Client > = Client::with_config(config); +//! // Use `Box` or `std::sync::Arc` to wrap the config +//! let config = Box::new(OpenAIConfig::default()) as Box; +//! // A function can now accept a `&Client>` parameter +//! // which can invoke any openai compatible api +//! let client: Client> = Client::with_config(config); //! ``` //! //! ## Microsoft Azure @@ -140,10 +140,13 @@ pub(crate) use async_openai_macros::byot; #[cfg(not(feature = "byot"))] pub(crate) use async_openai_macros::byot_passthrough as byot; +mod admin; +mod admin_api_keys; mod assistants; mod audio; mod audit_logs; mod batches; +mod certificates; mod chat; mod chatkit; mod client; @@ -167,9 +170,13 @@ mod messages; mod model; mod moderation; mod project_api_keys; +mod project_certificates; +mod project_rate_limits; mod project_service_accounts; mod project_users; mod projects; +#[cfg(feature = "realtime")] +mod realtime; mod responses; mod runs; mod speech; @@ -180,6 +187,7 @@ mod transcriptions; mod translations; pub mod types; mod uploads; +mod usage; mod users; mod util; mod vector_store_file_batches; @@ -189,10 +197,13 @@ mod video; #[cfg(feature = "webhook")] pub mod webhooks; +pub use admin::Admin; +pub use admin_api_keys::AdminAPIKeys; pub use assistants::Assistants; pub use audio::Audio; pub use audit_logs::AuditLogs; pub use batches::Batches; +pub use certificates::Certificates; pub use chat::Chat; pub use chatkit::Chatkit; pub use client::Client; @@ -213,9 +224,13 @@ pub use messages::Messages; pub use model::Models; pub use moderation::Moderations; pub use project_api_keys::ProjectAPIKeys; +pub use project_certificates::ProjectCertificates; +pub use project_rate_limits::ProjectRateLimits; pub use project_service_accounts::ProjectServiceAccounts; pub use project_users::ProjectUsers; pub use projects::Projects; +#[cfg(feature = "realtime")] +pub use realtime::Realtime; pub use responses::Responses; pub use runs::Runs; pub use speech::Speech; @@ -224,6 +239,7 @@ pub use threads::Threads; pub use transcriptions::Transcriptions; pub use translations::Translations; pub use uploads::Uploads; +pub use usage::Usage; pub use users::Users; pub use vector_store_file_batches::VectorStoreFileBatches; pub use vector_store_files::VectorStoreFiles; diff --git a/async-openai/src/messages.rs b/async-openai/src/messages.rs index 9368e114..cd59bed4 100644 --- a/async-openai/src/messages.rs +++ b/async-openai/src/messages.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ + types::assistants::{ CreateMessageRequest, DeleteMessageResponse, ListMessagesResponse, MessageObject, ModifyMessageRequest, }, diff --git a/async-openai/src/project_api_keys.rs b/async-openai/src/project_api_keys.rs index 6f3778d0..ce5d56c1 100644 --- a/async-openai/src/project_api_keys.rs +++ b/async-openai/src/project_api_keys.rs @@ -3,7 +3,9 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ProjectApiKey, ProjectApiKeyDeleteResponse, ProjectApiKeyListResponse}, + types::admin::project_api_keys::{ + ProjectApiKey, ProjectApiKeyDeleteResponse, ProjectApiKeyListResponse, + }, Client, }; diff --git a/async-openai/src/project_certificates.rs b/async-openai/src/project_certificates.rs new file mode 100644 index 00000000..a7cc2dd1 --- /dev/null +++ b/async-openai/src/project_certificates.rs @@ -0,0 +1,72 @@ +use serde::Serialize; + +use crate::{ + config::Config, + error::OpenAIError, + types::admin::certificates::{ListCertificatesResponse, ToggleCertificatesRequest}, + Client, +}; + +/// Manage certificates for a given project. Supports listing, activating, and deactivating certificates. +pub struct ProjectCertificates<'c, C: Config> { + client: &'c Client, + pub project_id: String, +} + +impl<'c, C: Config> ProjectCertificates<'c, C> { + pub fn new(client: &'c Client, project_id: &str) -> Self { + Self { + client, + project_id: project_id.into(), + } + } + + /// List all certificates for this project. + pub async fn list(&self, query: &Q) -> Result + where + Q: Serialize + ?Sized, + { + self.client + .get_with_query( + format!("/organization/projects/{}/certificates", self.project_id).as_str(), + query, + ) + .await + } + + /// Activate certificates for this project. + /// You can atomically and idempotently activate up to 10 certificates at a time. + pub async fn activate( + &self, + request: ToggleCertificatesRequest, + ) -> Result { + self.client + .post( + format!( + "/organization/projects/{}/certificates/activate", + self.project_id + ) + .as_str(), + request, + ) + .await + } + + /// Deactivate certificates for this project. + /// You can atomically and idempotently deactivate up to 10 certificates at a time. + pub async fn deactivate( + &self, + request: ToggleCertificatesRequest, + ) -> Result { + self.client + .post( + format!( + "/organization/projects/{}/certificates/deactivate", + self.project_id + ) + .as_str(), + request, + ) + .await + } +} diff --git a/async-openai/src/project_rate_limits.rs b/async-openai/src/project_rate_limits.rs new file mode 100644 index 00000000..13c7e643 --- /dev/null +++ b/async-openai/src/project_rate_limits.rs @@ -0,0 +1,58 @@ +use serde::Serialize; + +use crate::{ + config::Config, + error::OpenAIError, + types::admin::project_rate_limits::{ + ProjectRateLimit, ProjectRateLimitListResponse, ProjectRateLimitUpdateRequest, + }, + Client, +}; + +/// Manage rate limits for a given project. Supports listing and updating rate limits per model. +pub struct ProjectRateLimits<'c, C: Config> { + client: &'c Client, + pub project_id: String, +} + +impl<'c, C: Config> ProjectRateLimits<'c, C> { + pub fn new(client: &'c Client, project_id: &str) -> Self { + Self { + client, + project_id: project_id.into(), + } + } + + /// Returns the rate limits per model for a project. + #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)] + pub async fn list(&self, query: &Q) -> Result + where + Q: Serialize + ?Sized, + { + self.client + .get_with_query( + format!("/organization/projects/{}/rate_limits", self.project_id).as_str(), + &query, + ) + .await + } + + /// Updates a project rate limit. + #[crate::byot(T0 = std::fmt::Display, T1 = serde::Serialize, R = serde::de::DeserializeOwned)] + pub async fn update( + &self, + rate_limit_id: &str, + request: ProjectRateLimitUpdateRequest, + ) -> Result { + self.client + .post( + format!( + "/organization/projects/{}/rate_limits/{rate_limit_id}", + self.project_id + ) + .as_str(), + request, + ) + .await + } +} diff --git a/async-openai/src/project_service_accounts.rs b/async-openai/src/project_service_accounts.rs index 04b02aaf..53548e21 100644 --- a/async-openai/src/project_service_accounts.rs +++ b/async-openai/src/project_service_accounts.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ + types::admin::project_service_accounts::{ ProjectServiceAccount, ProjectServiceAccountCreateRequest, ProjectServiceAccountCreateResponse, ProjectServiceAccountDeleteResponse, ProjectServiceAccountListResponse, diff --git a/async-openai/src/project_users.rs b/async-openai/src/project_users.rs index bd790d5a..658031b2 100644 --- a/async-openai/src/project_users.rs +++ b/async-openai/src/project_users.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ + types::admin::project_users::{ ProjectUser, ProjectUserCreateRequest, ProjectUserDeleteResponse, ProjectUserListResponse, ProjectUserUpdateRequest, }, diff --git a/async-openai/src/projects.rs b/async-openai/src/projects.rs index 61b4f9e2..bdbdcc2a 100644 --- a/async-openai/src/projects.rs +++ b/async-openai/src/projects.rs @@ -4,7 +4,11 @@ use crate::{ config::Config, error::OpenAIError, project_api_keys::ProjectAPIKeys, - types::{Project, ProjectCreateRequest, ProjectListResponse, ProjectUpdateRequest}, + project_certificates::ProjectCertificates, + project_rate_limits::ProjectRateLimits, + types::admin::projects::{ + Project, ProjectCreateRequest, ProjectListResponse, ProjectUpdateRequest, + }, Client, ProjectServiceAccounts, ProjectUsers, }; @@ -34,6 +38,16 @@ impl<'c, C: Config> Projects<'c, C> { ProjectAPIKeys::new(self.client, project_id) } + // call [ProjectRateLimits] group APIs + pub fn rate_limits(&self, project_id: &str) -> ProjectRateLimits<'_, C> { + ProjectRateLimits::new(self.client, project_id) + } + + // call [ProjectCertificates] group APIs + pub fn certificates(&self, project_id: &str) -> ProjectCertificates<'_, C> { + ProjectCertificates::new(self.client, project_id) + } + /// Returns a list of projects. #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)] pub async fn list(&self, query: &Q) -> Result diff --git a/async-openai/src/realtime.rs b/async-openai/src/realtime.rs new file mode 100644 index 00000000..bb75d217 --- /dev/null +++ b/async-openai/src/realtime.rs @@ -0,0 +1,103 @@ +use crate::{ + config::Config, + error::OpenAIError, + types::realtime::{ + RealtimeCallAcceptRequest, RealtimeCallCreateRequest, RealtimeCallCreateResponse, + RealtimeCallReferRequest, RealtimeCallRejectRequest, RealtimeCreateClientSecretRequest, + RealtimeCreateClientSecretResponse, + }, + Client, +}; + +/// Realtime API for creating sessions, managing calls, and handling WebRTC connections. +/// Related guide: [Realtime API](https://platform.openai.com/docs/guides/realtime) +pub struct Realtime<'c, C: Config> { + client: &'c Client, +} + +impl<'c, C: Config> Realtime<'c, C> { + pub fn new(client: &'c Client) -> Self { + Self { client } + } + + /// Create a new Realtime API call over WebRTC and receive the SDP answer needed + /// to complete the peer connection. + /// + /// Returns the SDP answer in the response body and the call ID in the Location header. + pub async fn create_call( + &self, + request: RealtimeCallCreateRequest, + ) -> Result { + let (bytes, headers) = self + .client + .post_form_raw("/realtime/calls", request) + .await?; + + // Extract Location header + let location = headers + .get("location") + .and_then(|v| v.to_str().ok()) + .map(|s| s.to_string()); + + if location.is_none() { + tracing::warn!("Location header not found in Realtime call creation response"); + } + + // Use from_utf8_lossy to handle any invalid UTF-8 bytes in SDP + let sdp = String::from_utf8_lossy(&bytes).into_owned(); + + Ok(RealtimeCallCreateResponse { sdp, location }) + } + + /// Accept an incoming SIP call and configure the realtime session that will + /// handle the call. + pub async fn accept_call( + &self, + call_id: &str, + request: RealtimeCallAcceptRequest, + ) -> Result<(), OpenAIError> { + self.client + .post(&format!("/realtime/calls/{}/accept", call_id), request) + .await + } + + /// End an active Realtime API call, whether it was initiated over SIP or WebRTC. + pub async fn hangup_call(&self, call_id: &str) -> Result<(), OpenAIError> { + self.client + .post(&format!("/realtime/calls/{}/hangup", call_id), ()) + .await + } + + /// Transfer a SIP call to a new destination using the Realtime API. + pub async fn refer_call( + &self, + call_id: &str, + request: RealtimeCallReferRequest, + ) -> Result<(), OpenAIError> { + self.client + .post(&format!("/realtime/calls/{}/refer", call_id), request) + .await + } + + /// Decline an incoming SIP call handled by the Realtime API. + pub async fn reject_call( + &self, + call_id: &str, + request: Option, + ) -> Result<(), OpenAIError> { + self.client + .post( + &format!("/realtime/calls/{}/reject", call_id), + request.unwrap_or_default(), + ) + .await + } + + /// Create a Realtime client secret with an associated session configuration. + pub async fn create_client_secret( + &self, + request: RealtimeCreateClientSecretRequest, + ) -> Result { + self.client.post("/realtime/client_secrets", request).await + } +} diff --git a/async-openai/src/runs.rs b/async-openai/src/runs.rs index d0c5c105..4fad1424 100644 --- a/async-openai/src/runs.rs +++ b/async-openai/src/runs.rs @@ -4,7 +4,7 @@ use crate::{ config::Config, error::OpenAIError, steps::Steps, - types::{ + types::assistants::{ AssistantEventStream, CreateRunRequest, ListRunsResponse, ModifyRunRequest, RunObject, SubmitToolOutputsRunRequest, }, diff --git a/async-openai/src/speech.rs b/async-openai/src/speech.rs index 9f6f3a8d..4bd160af 100644 --- a/async-openai/src/speech.rs +++ b/async-openai/src/speech.rs @@ -19,7 +19,7 @@ impl<'c, C: Config> Speech<'c, C> { &self, request: CreateSpeechRequest, ) -> Result { - let bytes = self.client.post_raw("/audio/speech", request).await?; + let (bytes, _headers) = self.client.post_raw("/audio/speech", request).await?; Ok(CreateSpeechResponse { bytes }) } diff --git a/async-openai/src/steps.rs b/async-openai/src/steps.rs index 924cda82..e82cb977 100644 --- a/async-openai/src/steps.rs +++ b/async-openai/src/steps.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::{ config::Config, error::OpenAIError, - types::{ListRunStepsResponse, RunStepObject}, + types::assistants::{ListRunStepsResponse, RunStepObject}, Client, }; diff --git a/async-openai/src/threads.rs b/async-openai/src/threads.rs index f0bba6fe..5a0fe354 100644 --- a/async-openai/src/threads.rs +++ b/async-openai/src/threads.rs @@ -1,7 +1,7 @@ use crate::{ config::Config, error::OpenAIError, - types::{ + types::assistants::{ AssistantEventStream, CreateThreadAndRunRequest, CreateThreadRequest, DeleteThreadResponse, ModifyThreadRequest, RunObject, ThreadObject, }, diff --git a/async-openai/src/transcriptions.rs b/async-openai/src/transcriptions.rs index 9d776c5a..d2cf1f59 100644 --- a/async-openai/src/transcriptions.rs +++ b/async-openai/src/transcriptions.rs @@ -98,8 +98,10 @@ impl<'c, C: Config> Transcriptions<'c, C> { &self, request: CreateTranscriptionRequest, ) -> Result { - self.client + let (bytes, _headers) = self + .client .post_form_raw("/audio/transcriptions", request) - .await + .await?; + Ok(bytes) } } diff --git a/async-openai/src/translations.rs b/async-openai/src/translations.rs index 1c5797af..93260499 100644 --- a/async-openai/src/translations.rs +++ b/async-openai/src/translations.rs @@ -50,8 +50,10 @@ impl<'c, C: Config> Translations<'c, C> { &self, request: CreateTranslationRequest, ) -> Result { - self.client + let (bytes, _headers) = self + .client .post_form_raw("/audio/translations", request) - .await + .await?; + Ok(bytes) } } diff --git a/async-openai/src/types/admin/api_keys.rs b/async-openai/src/types/admin/api_keys.rs new file mode 100644 index 00000000..0be8723f --- /dev/null +++ b/async-openai/src/types/admin/api_keys.rs @@ -0,0 +1,79 @@ +use crate::types::OpenAIError; +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; + +/// Represents an individual Admin API key in an org. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct AdminApiKey { + /// The object type, which is always `organization.admin_api_key`. + pub object: String, + /// The identifier, which can be referenced in API endpoints. + pub id: String, + /// The name of the API key. + pub name: String, + /// The redacted value of the API key. + pub redacted_value: String, + /// The value of the API key. Only shown on create. + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + /// The Unix timestamp (in seconds) of when the API key was created. + pub created_at: u64, + /// The Unix timestamp (in seconds) of when the API key was last used. + pub last_used_at: Option, + /// The owner of the API key. + pub owner: AdminApiKeyOwner, +} + +/// Represents the owner of an admin API key. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct AdminApiKeyOwner { + pub r#type: String, + + pub object: String, + /// The identifier, which can be referenced in API endpoints. + pub id: String, + /// The name of the owner. + pub name: String, + /// The Unix timestamp (in seconds) of when the owner was created. + pub created_at: u64, + + pub role: String, +} + +/// Represents the response object for listing admin API keys. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct ApiKeyList { + /// The object type, which is always `list`. + pub object: String, + /// The list of admin API keys. + pub data: Vec, + /// Indicates if there are more admin API keys available. + pub has_more: bool, + /// The ID of the first admin API key in the list. + pub first_id: String, + /// The ID of the last admin API key in the list. + pub last_id: String, +} + +/// Represents the request object for creating an admin API key. +#[derive(Debug, Serialize, Deserialize, Builder, Clone, PartialEq)] +#[builder(name = "CreateAdminApiKeyRequestArgs")] +#[builder(pattern = "mutable")] +#[builder(setter(into, strip_option))] +#[builder(derive(Debug))] +#[builder(build_fn(error = "OpenAIError"))] +pub struct CreateAdminApiKeyRequest { + /// The name of the API key being created. + pub name: String, +} + +/// Represents the response object for deleting an admin API key. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct AdminApiKeyDeleteResponse { + /// The object type, which is always `organization.admin_api_key.deleted`. + pub object: String, + /// The ID of the deleted API key. + pub id: String, + /// Indicates if the API key was successfully deleted. + pub deleted: bool, +} diff --git a/async-openai/src/types/audit_log.rs b/async-openai/src/types/admin/audit_logs.rs similarity index 99% rename from async-openai/src/types/audit_log.rs rename to async-openai/src/types/admin/audit_logs.rs index d652cf40..1d3b137e 100644 --- a/async-openai/src/types/audit_log.rs +++ b/async-openai/src/types/admin/audit_logs.rs @@ -133,7 +133,7 @@ pub struct AuditLog { /// The event type. pub r#type: AuditLogEventType, /// The Unix timestamp (in seconds) of the event. - pub effective_at: u32, + pub effective_at: u64, /// The project that the action was scoped to. Absent for actions not scoped to projects. pub project: Option, /// The actor who performed the audit logged action. diff --git a/async-openai/src/types/admin/certificates.rs b/async-openai/src/types/admin/certificates.rs new file mode 100644 index 00000000..e10774f0 --- /dev/null +++ b/async-openai/src/types/admin/certificates.rs @@ -0,0 +1,100 @@ +use crate::error::OpenAIError; +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; + +/// Represents an individual certificate uploaded to the organization. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct Certificate { + /// The object type. Can be `certificate`, `organization.certificate`, or `organization.project.certificate`. + pub object: String, + /// The identifier, which can be referenced in API endpoints. + pub id: String, + /// The name of the certificate. + pub name: String, + /// The Unix timestamp (in seconds) of when the certificate was uploaded. + pub created_at: u64, + /// Details about the certificate. + pub certificate_details: CertificateDetails, + /// Whether the certificate is currently active at the specified scope. + /// Not returned when getting details for a specific certificate. + #[serde(skip_serializing_if = "Option::is_none")] + pub active: Option, +} + +/// Details about a certificate. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CertificateDetails { + /// The Unix timestamp (in seconds) of when the certificate becomes valid. + pub valid_at: u64, + /// The Unix timestamp (in seconds) of when the certificate expires. + pub expires_at: u64, + /// The content of the certificate in PEM format. + /// Only included when requested via the `include[]=content` query parameter. + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, +} + +/// Response for listing certificates. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct ListCertificatesResponse { + /// The object type, which is always `list`. + pub object: String, + /// The list of certificates. + pub data: Vec, + /// The ID of the first certificate in the list. + #[serde(skip_serializing_if = "Option::is_none")] + pub first_id: Option, + /// The ID of the last certificate in the list. + #[serde(skip_serializing_if = "Option::is_none")] + pub last_id: Option, + /// Indicates if there are more certificates available. + pub has_more: bool, +} + +/// Request for uploading a certificate. +#[derive(Debug, Serialize, Deserialize, Builder, Clone, PartialEq)] +#[builder(name = "UploadCertificateRequestArgs")] +#[builder(pattern = "mutable")] +#[builder(setter(into, strip_option))] +#[builder(derive(Debug))] +#[builder(build_fn(error = "OpenAIError"))] +pub struct UploadCertificateRequest { + /// An optional name for the certificate. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The certificate content in PEM format. + pub content: String, +} + +/// Request for modifying a certificate. +#[derive(Debug, Serialize, Deserialize, Builder, Clone, PartialEq)] +#[builder(name = "ModifyCertificateRequestArgs")] +#[builder(pattern = "mutable")] +#[builder(setter(into, strip_option))] +#[builder(derive(Debug))] +#[builder(build_fn(error = "OpenAIError"))] +pub struct ModifyCertificateRequest { + /// The updated name for the certificate. + pub name: String, +} + +/// Request for toggling (activating/deactivating) certificates. +#[derive(Debug, Serialize, Deserialize, Builder, Clone, PartialEq)] +#[builder(name = "ToggleCertificatesRequestArgs")] +#[builder(pattern = "mutable")] +#[builder(setter(into, strip_option))] +#[builder(derive(Debug))] +#[builder(build_fn(error = "OpenAIError"))] +pub struct ToggleCertificatesRequest { + /// Array of certificate IDs to toggle (1-10 certificates). + pub certificate_ids: Vec, +} + +/// Response for deleting a certificate. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct DeleteCertificateResponse { + /// The object type, which is always `certificate.deleted`. + pub object: String, + /// The ID of the certificate that was deleted. + pub id: String, +} diff --git a/async-openai/src/types/invites.rs b/async-openai/src/types/admin/invites.rs similarity index 93% rename from async-openai/src/types/invites.rs rename to async-openai/src/types/admin/invites.rs index 282b3e02..a327af83 100644 --- a/async-openai/src/types/invites.rs +++ b/async-openai/src/types/admin/invites.rs @@ -1,9 +1,8 @@ use crate::types::OpenAIError; +use crate::types::OrganizationRole; use derive_builder::Builder; use serde::{Deserialize, Serialize}; -use super::OrganizationRole; - #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] #[serde(rename_all = "lowercase")] pub enum InviteStatus { @@ -54,9 +53,9 @@ pub struct Invite { /// `accepted`, `expired`, or `pending` pub status: InviteStatus, /// The Unix timestamp (in seconds) of when the invite was sent. - pub invited_at: u32, + pub invited_at: u64, /// The Unix timestamp (in seconds) of when the invite expires. - pub expires_at: u32, + pub expires_at: u64, /// The Unix timestamp (in seconds) of when the invite was accepted. - pub accepted_at: Option, + pub accepted_at: Option, } diff --git a/async-openai/src/types/admin/mod.rs b/async-openai/src/types/admin/mod.rs new file mode 100644 index 00000000..396f3ca5 --- /dev/null +++ b/async-openai/src/types/admin/mod.rs @@ -0,0 +1,13 @@ +//! Admin-related types for organization management APIs. + +pub mod api_keys; +pub mod audit_logs; +pub mod certificates; +pub mod invites; +pub mod project_api_keys; +pub mod project_rate_limits; +pub mod project_service_accounts; +pub mod project_users; +pub mod projects; +pub mod usage; +pub mod users; diff --git a/async-openai/src/types/project_api_key.rs b/async-openai/src/types/admin/project_api_keys.rs similarity index 93% rename from async-openai/src/types/project_api_key.rs rename to async-openai/src/types/admin/project_api_keys.rs index 96886581..2d339d7b 100644 --- a/async-openai/src/types/project_api_key.rs +++ b/async-openai/src/types/admin/project_api_keys.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; -use super::{ProjectServiceAccount, ProjectUser}; +use crate::types::admin::project_service_accounts::ProjectServiceAccount; +use crate::types::admin::project_users::ProjectUser; /// Represents an individual API key in a project. #[derive(Debug, Serialize, Deserialize)] @@ -12,7 +13,7 @@ pub struct ProjectApiKey { /// The name of the API key. pub name: String, /// The Unix timestamp (in seconds) of when the API key was created. - pub created_at: u32, + pub created_at: u64, /// The identifier, which can be referenced in API endpoints. pub id: String, /// The owner of the API key. diff --git a/async-openai/src/types/admin/project_rate_limits.rs b/async-openai/src/types/admin/project_rate_limits.rs new file mode 100644 index 00000000..fdadb03b --- /dev/null +++ b/async-openai/src/types/admin/project_rate_limits.rs @@ -0,0 +1,68 @@ +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; + +use crate::types::OpenAIError; + +/// Represents a project rate limit config. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct ProjectRateLimit { + /// The object type, which is always `project.rate_limit` + pub object: String, + /// The identifier, which can be referenced in API endpoints. + pub id: String, + /// The model this rate limit applies to. + pub model: String, + /// The maximum requests per minute. + pub max_requests_per_1_minute: i64, + /// The maximum tokens per minute. + pub max_tokens_per_1_minute: i64, + /// The maximum images per minute. Only present for relevant models. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_images_per_1_minute: Option, + /// The maximum audio megabytes per minute. Only present for relevant models. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_audio_megabytes_per_1_minute: Option, + /// The maximum requests per day. Only present for relevant models. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_requests_per_1_day: Option, + /// The maximum batch input tokens per day. Only present for relevant models. + #[serde(skip_serializing_if = "Option::is_none")] + pub batch_1_day_max_input_tokens: Option, +} + +/// Represents the response object for listing project rate limits. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct ProjectRateLimitListResponse { + /// The object type, which is always `list`. + pub object: String, + /// The list of project rate limits. + pub data: Vec, + /// The ID of the first project rate limit in the list. + pub first_id: String, + /// The ID of the last project rate limit in the list. + pub last_id: String, + /// Indicates if there are more project rate limits available. + pub has_more: bool, +} + +/// The project rate limit update request payload. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Builder, Default)] +#[builder(name = "ProjectRateLimitUpdateRequestArgs")] +#[builder(pattern = "mutable")] +#[builder(setter(into, strip_option), default)] +#[builder(derive(Debug))] +#[builder(build_fn(error = "OpenAIError"))] +pub struct ProjectRateLimitUpdateRequest { + /// The maximum requests per minute. + pub max_requests_per_1_minute: Option, + /// The maximum tokens per minute. + pub max_tokens_per_1_minute: Option, + /// The maximum images per minute. Only relevant for certain models. + pub max_images_per_1_minute: Option, + /// The maximum audio megabytes per minute. Only relevant for certain models. + pub max_audio_megabytes_per_1_minute: Option, + /// The maximum requests per day. Only relevant for certain models. + pub max_requests_per_1_day: Option, + /// The maximum batch input tokens per day. Only relevant for certain models. + pub batch_1_day_max_input_tokens: Option, +} diff --git a/async-openai/src/types/project_service_account.rs b/async-openai/src/types/admin/project_service_accounts.rs similarity index 95% rename from async-openai/src/types/project_service_account.rs rename to async-openai/src/types/admin/project_service_accounts.rs index 4449ddf8..1ec1ae6a 100644 --- a/async-openai/src/types/project_service_account.rs +++ b/async-openai/src/types/admin/project_service_accounts.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::ProjectUserRole; +use crate::types::admin::project_users::ProjectUserRole; /// Represents an individual service account in a project. #[derive(Debug, Serialize, Deserialize)] @@ -14,7 +14,7 @@ pub struct ProjectServiceAccount { /// `owner` or `member`. pub role: ProjectUserRole, /// The Unix timestamp (in seconds) of when the service account was created. - pub created_at: u32, + pub created_at: u64, } /// Represents the response object for listing project service accounts. @@ -51,7 +51,7 @@ pub struct ProjectServiceAccountCreateResponse { /// Service accounts can only have one role of type `member`. pub role: String, /// The Unix timestamp (in seconds) of when the service account was created. - pub created_at: u32, + pub created_at: u64, /// The API key associated with the created service account. pub api_key: ProjectServiceAccountApiKey, } @@ -66,7 +66,7 @@ pub struct ProjectServiceAccountApiKey { /// The name of the API key. pub name: String, /// The Unix timestamp (in seconds) of when the API key was created. - pub created_at: u32, + pub created_at: u64, /// The ID of the API key. pub id: String, } diff --git a/async-openai/src/types/project_users.rs b/async-openai/src/types/admin/project_users.rs similarity index 98% rename from async-openai/src/types/project_users.rs rename to async-openai/src/types/admin/project_users.rs index 5bedd26a..51a88199 100644 --- a/async-openai/src/types/project_users.rs +++ b/async-openai/src/types/admin/project_users.rs @@ -16,7 +16,7 @@ pub struct ProjectUser { /// `owner` or `member` pub role: ProjectUserRole, /// The Unix timestamp (in seconds) of when the project was added. - pub added_at: u32, + pub added_at: u64, } /// `owner` or `member` diff --git a/async-openai/src/types/projects.rs b/async-openai/src/types/admin/projects.rs similarity index 97% rename from async-openai/src/types/projects.rs rename to async-openai/src/types/admin/projects.rs index bb5ae3ff..20fdc309 100644 --- a/async-openai/src/types/projects.rs +++ b/async-openai/src/types/admin/projects.rs @@ -20,9 +20,9 @@ pub struct Project { /// The name of the project. This appears in reporting. pub name: String, /// The Unix timestamp (in seconds) of when the project was created. - pub created_at: u32, + pub created_at: u64, /// The Unix timestamp (in seconds) of when the project was archived or `null`. - pub archived_at: Option, + pub archived_at: Option, /// `active` or `archived` pub status: ProjectStatus, } diff --git a/async-openai/src/types/admin/usage.rs b/async-openai/src/types/admin/usage.rs new file mode 100644 index 00000000..dfdb8da1 --- /dev/null +++ b/async-openai/src/types/admin/usage.rs @@ -0,0 +1,315 @@ +use serde::{Deserialize, Serialize}; + +/// Width of each time bucket in response. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum UsageBucketWidth { + #[serde(rename = "1m")] + OneMinute, + #[serde(rename = "1h")] + OneHour, + #[serde(rename = "1d")] + OneDay, +} + +/// Fields to group usage data by. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum UsageGroupBy { + ProjectId, + UserId, + ApiKeyId, + Model, + Batch, + ServiceTier, +} + +/// Query parameters for organization usage endpoints. +#[derive(Debug, Clone, Serialize, Default)] +pub struct UsageQueryParams { + /// Start time (Unix seconds) of the query time range, inclusive. + pub start_time: u64, + /// End time (Unix seconds) of the query time range, exclusive. + #[serde(skip_serializing_if = "Option::is_none")] + pub end_time: Option, + /// Width of each time bucket in response. Currently `1m`, `1h` and `1d` are supported, default to `1d`. + #[serde(skip_serializing_if = "Option::is_none")] + pub bucket_width: Option, + /// Return only usage for these projects. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_ids: Option>, + /// Return only usage for these users. + #[serde(skip_serializing_if = "Option::is_none")] + pub user_ids: Option>, + /// Return only usage for these API keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub api_key_ids: Option>, + /// Return only usage for these models. + #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option>, + /// If `true`, return batch jobs only. If `false`, return non-batch jobs only. By default, return both. + #[serde(skip_serializing_if = "Option::is_none")] + pub batch: Option, + /// Group the usage data by the specified fields. + #[serde(skip_serializing_if = "Option::is_none")] + pub group_by: Option>, + /// Specifies the number of buckets to return. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + /// A cursor for use in pagination. Corresponding to the `next_page` field from the previous response. + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, +} + +/// Response structure for organization usage endpoints. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageResponse { + /// The object type, which is always `page`. + pub object: String, + /// List of time buckets containing usage data. + pub data: Vec, + /// Whether there are more pages available. + pub has_more: bool, + /// Cursor for the next page. + pub next_page: Option, +} + +/// A time bucket containing usage results. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageTimeBucket { + /// The object type, which is always `bucket`. + pub object: String, + /// Start time of the bucket (Unix seconds). + pub start_time: u64, + /// End time of the bucket (Unix seconds). + pub end_time: u64, + /// Start time of the bucket in ISO 8601 format. + #[serde(default)] + pub start_time_iso: Option, + /// End time of the bucket in ISO 8601 format. + #[serde(default)] + pub end_time_iso: Option, + /// Usage results for this time bucket. + pub results: Vec, +} + +/// Discriminated union of all possible usage result types. +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum UsageResult { + AudioSpeeches(UsageAudioSpeechesResult), + AudioTranscriptions(UsageAudioTranscriptionsResult), + CodeInterpreterSessions(UsageCodeInterpreterSessionsResult), + Completions(UsageCompletionsResult), + Embeddings(UsageEmbeddingsResult), + Images(UsageImagesResult), + Moderations(UsageModerationsResult), + VectorStores(UsageVectorStoresResult), + Costs(CostsResult), +} + +/// The aggregated audio speeches usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageAudioSpeechesResult { + /// The object type, which is always `organization.usage.audio_speeches.result`. + pub object: String, + /// The number of characters processed. + pub characters: u64, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, +} + +/// The aggregated audio transcriptions usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageAudioTranscriptionsResult { + /// The object type, which is always `organization.usage.audio_transcriptions.result`. + pub object: String, + /// The number of seconds processed. + pub seconds: u64, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, +} + +/// The aggregated code interpreter sessions usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageCodeInterpreterSessionsResult { + /// The object type, which is always `organization.usage.code_interpreter_sessions.result`. + pub object: String, + /// The number of code interpreter sessions. + pub num_sessions: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, +} + +/// The aggregated completions usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageCompletionsResult { + /// The object type, which is always `organization.usage.completions.result`. + pub object: String, + /// The aggregated number of text input tokens used, including cached tokens. For customers subscribe to scale tier, this includes scale tier tokens. + pub input_tokens: u64, + /// The aggregated number of text output tokens used. For customers subscribe to scale tier, this includes scale tier tokens. + pub output_tokens: u64, + /// The aggregated number of text input tokens that has been cached from previous requests. For customers subscribe to scale tier, this includes scale tier tokens. + #[serde(default)] + pub input_cached_tokens: Option, + /// The aggregated number of uncached input tokens. + #[serde(default)] + pub input_uncached_tokens: Option, + /// The aggregated number of text input tokens used. + #[serde(default)] + pub input_text_tokens: Option, + /// The aggregated number of text output tokens used. + #[serde(default)] + pub output_text_tokens: Option, + /// The aggregated number of cached text input tokens. + #[serde(default)] + pub input_cached_text_tokens: Option, + /// The aggregated number of audio input tokens used, including cached tokens. + #[serde(default)] + pub input_audio_tokens: Option, + /// The aggregated number of cached audio input tokens. + #[serde(default)] + pub input_cached_audio_tokens: Option, + /// The aggregated number of audio output tokens used. + #[serde(default)] + pub output_audio_tokens: Option, + /// The aggregated number of image input tokens used. + #[serde(default)] + pub input_image_tokens: Option, + /// The aggregated number of cached image input tokens. + #[serde(default)] + pub input_cached_image_tokens: Option, + /// The aggregated number of image output tokens used. + #[serde(default)] + pub output_image_tokens: Option, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, + /// When `group_by=batch`, this field tells whether the grouped usage result is batch or not. + pub batch: Option, + /// When `group_by=service_tier`, this field provides the service tier of the grouped usage result. + pub service_tier: Option, +} + +/// The aggregated embeddings usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageEmbeddingsResult { + /// The object type, which is always `organization.usage.embeddings.result`. + pub object: String, + /// The aggregated number of input tokens used. + pub input_tokens: u64, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, +} + +/// The aggregated images usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageImagesResult { + /// The object type, which is always `organization.usage.images.result`. + pub object: String, + /// The number of images processed. + pub images: u64, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=source`, this field provides the source of the grouped usage result, possible values are `image.generation`, `image.edit`, `image.variation`. + pub source: Option, + /// When `group_by=size`, this field provides the image size of the grouped usage result. + pub size: Option, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, +} + +/// The aggregated moderations usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageModerationsResult { + /// The object type, which is always `organization.usage.moderations.result`. + pub object: String, + /// The aggregated number of input tokens used. + pub input_tokens: u64, + /// The count of requests made to the model. + pub num_model_requests: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, + /// When `group_by=user_id`, this field provides the user ID of the grouped usage result. + pub user_id: Option, + /// When `group_by=api_key_id`, this field provides the API key ID of the grouped usage result. + pub api_key_id: Option, + /// When `group_by=model`, this field provides the model name of the grouped usage result. + pub model: Option, +} + +/// The aggregated vector stores usage details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct UsageVectorStoresResult { + /// The object type, which is always `organization.usage.vector_stores.result`. + pub object: String, + /// The vector stores usage in bytes. + pub usage_bytes: u64, + /// When `group_by=project_id`, this field provides the project ID of the grouped usage result. + pub project_id: Option, +} + +/// The aggregated costs details of the specific time bucket. +#[derive(Debug, Clone, Deserialize)] +pub struct CostsResult { + /// The object type, which is always `organization.costs.result`. + pub object: String, + /// The monetary value in its associated currency. + pub amount: CostsAmount, + /// When `group_by=line_item`, this field provides the line item of the grouped costs result. + pub line_item: Option, + /// When `group_by=project_id`, this field provides the project ID of the grouped costs result. + pub project_id: Option, + /// The organization ID. + #[serde(default)] + pub organization_id: Option, +} + +/// The monetary value in its associated currency. +#[derive(Debug, Clone, Deserialize)] +pub struct CostsAmount { + /// The numeric value of the cost. + pub value: f64, + /// Lowercase ISO-4217 currency e.g. "usd" + pub currency: String, +} diff --git a/async-openai/src/types/users.rs b/async-openai/src/types/admin/users.rs similarity index 96% rename from async-openai/src/types/users.rs rename to async-openai/src/types/admin/users.rs index 5fd0760c..8ed29112 100644 --- a/async-openai/src/types/users.rs +++ b/async-openai/src/types/admin/users.rs @@ -1,9 +1,8 @@ use crate::types::OpenAIError; +use crate::types::OrganizationRole; use derive_builder::Builder; use serde::{Deserialize, Serialize}; -use super::OrganizationRole; - /// Represents an individual `user` within an organization. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct User { @@ -18,7 +17,7 @@ pub struct User { /// `owner` or `reader` pub role: OrganizationRole, /// The Unix timestamp (in seconds) of when the users was added. - pub added_at: u32, + pub added_at: u64, } /// A list of `User` objects. diff --git a/async-openai/src/types/assistant.rs b/async-openai/src/types/assistants/assistant.rs similarity index 99% rename from async-openai/src/types/assistant.rs rename to async-openai/src/types/assistants/assistant.rs index 9e53bbe1..a4717d50 100644 --- a/async-openai/src/types/assistant.rs +++ b/async-openai/src/types/assistants/assistant.rs @@ -85,7 +85,7 @@ pub struct AssistantObject { /// The object type, which is always `assistant`. pub object: String, /// The Unix timestamp (in seconds) for when the assistant was created. - pub created_at: i32, + pub created_at: u64, /// The name of the assistant. The maximum length is 256 characters. pub name: Option, /// The description of the assistant. The maximum length is 512 characters. diff --git a/async-openai/src/types/assistant_impls.rs b/async-openai/src/types/assistants/assistant_impls.rs similarity index 92% rename from async-openai/src/types/assistant_impls.rs rename to async-openai/src/types/assistants/assistant_impls.rs index f1bc03dc..f693fcac 100644 --- a/async-openai/src/types/assistant_impls.rs +++ b/async-openai/src/types/assistants/assistant_impls.rs @@ -1,8 +1,9 @@ -use crate::types::{ - chat::FunctionObject, AssistantToolCodeInterpreterResources, AssistantToolFileSearchResources, +use crate::types::assistants::{ + AssistantToolCodeInterpreterResources, AssistantToolFileSearchResources, AssistantToolResources, AssistantTools, AssistantToolsFileSearch, AssistantToolsFunction, CreateAssistantToolFileSearchResources, CreateAssistantToolResources, }; +use crate::types::chat::FunctionObject; impl From for AssistantTools { fn from(value: AssistantToolsFileSearch) -> Self { diff --git a/async-openai/src/types/assistant_stream.rs b/async-openai/src/types/assistants/assistant_stream.rs similarity index 99% rename from async-openai/src/types/assistant_stream.rs rename to async-openai/src/types/assistants/assistant_stream.rs index 810ba6e6..1037b8ba 100644 --- a/async-openai/src/types/assistant_stream.rs +++ b/async-openai/src/types/assistants/assistant_stream.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::error::{map_deserialization_error, ApiError, OpenAIError, StreamError}; -use super::{ +use crate::types::assistants::{ MessageDeltaObject, MessageObject, RunObject, RunStepDeltaObject, RunStepObject, ThreadObject, }; diff --git a/async-openai/src/types/message.rs b/async-openai/src/types/assistants/message.rs similarity index 99% rename from async-openai/src/types/message.rs rename to async-openai/src/types/assistants/message.rs index e3f00ea9..ab551f0f 100644 --- a/async-openai/src/types/message.rs +++ b/async-openai/src/types/assistants/message.rs @@ -47,7 +47,7 @@ pub struct MessageObject { /// The object type, which is always `thread.message`. pub object: String, /// The Unix timestamp (in seconds) for when the message was created. - pub created_at: i32, + pub created_at: u64, /// The [thread](https://platform.openai.com/docs/api-reference/threads) ID that this message belongs to. pub thread_id: String, @@ -58,10 +58,10 @@ pub struct MessageObject { pub incomplete_details: Option, /// The Unix timestamp (in seconds) for when the message was completed. - pub completed_at: Option, + pub completed_at: Option, /// The Unix timestamp (in seconds) for when the message was marked as incomplete. - pub incomplete_at: Option, + pub incomplete_at: Option, /// The entity that produced the message. One of `user` or `assistant`. pub role: MessageRole, @@ -286,7 +286,7 @@ pub enum MessageDeltaContent { #[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] pub struct MessageDeltaContentRefusalObject { /// The index of the refusal part in the message. - pub index: i32, + pub index: u32, pub refusal: Option, } diff --git a/async-openai/src/types/assistants/mod.rs b/async-openai/src/types/assistants/mod.rs new file mode 100644 index 00000000..b0debabf --- /dev/null +++ b/async-openai/src/types/assistants/mod.rs @@ -0,0 +1,14 @@ +mod assistant; +mod assistant_impls; +mod assistant_stream; +mod message; +mod run; +mod step; +mod thread; + +pub use assistant::*; +pub use assistant_stream::*; +pub use message::*; +pub use run::*; +pub use step::*; +pub use thread::*; diff --git a/async-openai/src/types/run.rs b/async-openai/src/types/assistants/run.rs similarity index 98% rename from async-openai/src/types/run.rs rename to async-openai/src/types/assistants/run.rs index e5355841..591db02b 100644 --- a/async-openai/src/types/run.rs +++ b/async-openai/src/types/assistants/run.rs @@ -18,7 +18,7 @@ pub struct RunObject { /// The object type, which is always `thread.run`. pub object: String, /// The Unix timestamp (in seconds) for when the run was created. - pub created_at: i32, + pub created_at: u64, ///The ID of the [thread](https://platform.openai.com/docs/api-reference/threads) that was executed on as a part of this run. pub thread_id: String, @@ -35,15 +35,15 @@ pub struct RunObject { pub last_error: Option, /// The Unix timestamp (in seconds) for when the run will expire. - pub expires_at: Option, + pub expires_at: Option, /// The Unix timestamp (in seconds) for when the run was started. - pub started_at: Option, + pub started_at: Option, /// The Unix timestamp (in seconds) for when the run was cancelled. - pub cancelled_at: Option, + pub cancelled_at: Option, /// The Unix timestamp (in seconds) for when the run failed. - pub failed_at: Option, + pub failed_at: Option, ///The Unix timestamp (in seconds) for when the run was completed. - pub completed_at: Option, + pub completed_at: Option, /// Details on why the run is incomplete. Will be `null` if the run is not incomplete. pub incomplete_details: Option, diff --git a/async-openai/src/types/step.rs b/async-openai/src/types/assistants/step.rs similarity index 98% rename from async-openai/src/types/step.rs rename to async-openai/src/types/assistants/step.rs index d95b3c18..390497e1 100644 --- a/async-openai/src/types/step.rs +++ b/async-openai/src/types/assistants/step.rs @@ -19,7 +19,7 @@ pub struct RunStepObject { /// The object type, which is always `thread.run.step`. pub object: String, /// The Unix timestamp (in seconds) for when the run step was created. - pub created_at: i32, + pub created_at: u64, /// The ID of the [assistant](https://platform.openai.com/docs/api-reference/assistants) associated with the run step. pub assistant_id: Option, @@ -43,16 +43,16 @@ pub struct RunStepObject { pub last_error: Option, ///The Unix timestamp (in seconds) for when the run step expired. A step is considered expired if the parent run is expired. - pub expires_at: Option, + pub expires_at: Option, /// The Unix timestamp (in seconds) for when the run step was cancelled. - pub cancelled_at: Option, + pub cancelled_at: Option, /// The Unix timestamp (in seconds) for when the run step failed. - pub failed_at: Option, + pub failed_at: Option, /// The Unix timestamp (in seconds) for when the run step completed. - pub completed_at: Option, + pub completed_at: Option, pub metadata: Option>, diff --git a/async-openai/src/types/thread.rs b/async-openai/src/types/assistants/thread.rs similarity index 99% rename from async-openai/src/types/thread.rs rename to async-openai/src/types/assistants/thread.rs index 199d8a46..7f45af6a 100644 --- a/async-openai/src/types/thread.rs +++ b/async-openai/src/types/assistants/thread.rs @@ -19,7 +19,7 @@ pub struct ThreadObject { /// The object type, which is always `thread`. pub object: String, /// The Unix timestamp (in seconds) for when the thread was created. - pub created_at: i32, + pub created_at: u64, /// A set of resources that are made available to the assistant's tools in this thread. The resources are specific to the type of tool. For example, the `code_interpreter` tool requires a list of file IDs, while the `file_search` tool requires a list of vector store IDs. pub tool_resources: Option, diff --git a/async-openai/src/types/audio/audio_types.rs b/async-openai/src/types/audio/audio_types.rs index e0ab2dcd..253c7009 100644 --- a/async-openai/src/types/audio/audio_types.rs +++ b/async-openai/src/types/audio/audio_types.rs @@ -348,10 +348,10 @@ pub struct TranscriptionDiarizedSegment { #[derive(Debug, Deserialize, Clone, Serialize)] pub struct TranscriptionSegment { /// Unique identifier of the segment. - pub id: i32, + pub id: u32, // Seek offset of the segment. - pub seek: i32, + pub seek: u32, /// Start time of the segment in seconds. pub start: f32, @@ -363,7 +363,7 @@ pub struct TranscriptionSegment { pub text: String, /// Array of token IDs for the text content. - pub tokens: Vec, + pub tokens: Vec, /// Temperature parameter used for generating the segment. pub temperature: f32, diff --git a/async-openai/src/types/batches/batch.rs b/async-openai/src/types/batches/batch.rs index 1148a31a..2fcc037f 100644 --- a/async-openai/src/types/batches/batch.rs +++ b/async-openai/src/types/batches/batch.rs @@ -92,23 +92,23 @@ pub struct Batch { /// The ID of the file containing the outputs of requests with errors. pub error_file_id: Option, /// The Unix timestamp (in seconds) for when the batch was created. - pub created_at: u32, + pub created_at: u64, /// The Unix timestamp (in seconds) for when the batch started processing. - pub in_progress_at: Option, + pub in_progress_at: Option, /// The Unix timestamp (in seconds) for when the batch will expire. - pub expires_at: Option, + pub expires_at: Option, /// The Unix timestamp (in seconds) for when the batch started finalizing. - pub finalizing_at: Option, + pub finalizing_at: Option, /// The Unix timestamp (in seconds) for when the batch was completed. - pub completed_at: Option, + pub completed_at: Option, /// The Unix timestamp (in seconds) for when the batch failed. - pub failed_at: Option, + pub failed_at: Option, /// The Unix timestamp (in seconds) for when the batch expired. - pub expired_at: Option, + pub expired_at: Option, /// The Unix timestamp (in seconds) for when the batch started cancelling. - pub cancelling_at: Option, + pub cancelling_at: Option, /// The Unix timestamp (in seconds) for when the batch was cancelled. - pub cancelled_at: Option, + pub cancelled_at: Option, /// The request counts for different statuses within the batch. pub request_counts: Option, /// Represents token usage details including input tokens, output tokens, a breakdown of output tokens, and the total tokens used. Only populated on batches created after September 7, 2025. diff --git a/async-openai/src/types/chat/chat_types.rs b/async-openai/src/types/chat/chat_types.rs index a56c414c..e5e1d40a 100644 --- a/async-openai/src/types/chat/chat_types.rs +++ b/async-openai/src/types/chat/chat_types.rs @@ -460,7 +460,7 @@ pub struct ChatCompletionResponseMessageAudio { /// Unique identifier for this audio response. pub id: String, /// The Unix timestamp (in seconds) for when this audio response will no longer be accessible on the server for use in multi-turn conversations. - pub expires_at: u32, + pub expires_at: u64, /// Base64 encoded audio bytes generated by the model, in the format specified in the request. pub data: String, /// Transcript of the audio generated by the model. diff --git a/async-openai/src/types/chatkit/session.rs b/async-openai/src/types/chatkit/session.rs index 4eed1dcf..1b458d16 100644 --- a/async-openai/src/types/chatkit/session.rs +++ b/async-openai/src/types/chatkit/session.rs @@ -14,7 +14,7 @@ pub struct ChatSessionResource { #[serde(default = "default_session_object")] pub object: String, /// Unix timestamp (in seconds) for when the session expires. - pub expires_at: i64, + pub expires_at: u64, /// Ephemeral client secret that authenticates session requests. pub client_secret: String, /// Workflow metadata for the session. @@ -24,7 +24,7 @@ pub struct ChatSessionResource { /// Resolved rate limit values. pub rate_limits: ChatSessionRateLimits, /// Convenience copy of the per-minute request limit. - pub max_requests_per_1_minute: i32, + pub max_requests_per_1_minute: u32, /// Current lifecycle state of the session. pub status: ChatSessionStatus, /// Resolved ChatKit feature configuration for the session. @@ -61,7 +61,7 @@ pub struct ChatkitWorkflowTracing { #[derive(Clone, Serialize, Debug, Deserialize, PartialEq)] pub struct ChatSessionRateLimits { /// Maximum allowed requests per one-minute window. - pub max_requests_per_1_minute: i32, + pub max_requests_per_1_minute: u32, } /// Current lifecycle state of the session. @@ -98,10 +98,10 @@ pub struct ChatSessionFileUpload { pub enabled: bool, /// Maximum upload size in megabytes. #[serde(skip_serializing_if = "Option::is_none")] - pub max_file_size: Option, + pub max_file_size: Option, /// Maximum number of uploads allowed during the session. #[serde(skip_serializing_if = "Option::is_none")] - pub max_files: Option, + pub max_files: Option, } /// History retention preferences returned for the session. @@ -111,7 +111,7 @@ pub struct ChatSessionHistory { pub enabled: bool, /// Number of prior threads surfaced in history views. Defaults to null when all history is retained. #[serde(skip_serializing_if = "Option::is_none")] - pub recent_threads: Option, + pub recent_threads: Option, } /// Parameters for provisioning a new ChatKit session. @@ -184,7 +184,7 @@ pub struct ExpiresAfterParam { #[builder(default = "default_anchor()")] pub anchor: String, /// Number of seconds after the anchor when the session expires. - pub seconds: i32, + pub seconds: u32, } fn default_anchor() -> String { @@ -201,7 +201,7 @@ fn default_anchor() -> String { pub struct RateLimitsParam { /// Maximum number of requests allowed per minute for the session. Defaults to 10. #[serde(skip_serializing_if = "Option::is_none")] - pub max_requests_per_1_minute: Option, + pub max_requests_per_1_minute: Option, } /// Optional per-session configuration settings for ChatKit behavior. @@ -249,10 +249,10 @@ pub struct FileUploadParam { pub enabled: Option, /// Maximum size in megabytes for each uploaded file. Defaults to 512 MB, which is the maximum allowable size. #[serde(skip_serializing_if = "Option::is_none")] - pub max_file_size: Option, + pub max_file_size: Option, /// Maximum number of files that can be uploaded to the session. Defaults to 10. #[serde(skip_serializing_if = "Option::is_none")] - pub max_files: Option, + pub max_files: Option, } /// Controls how much historical context is retained for the session. @@ -268,5 +268,5 @@ pub struct HistoryParam { pub enabled: Option, /// Number of recent ChatKit threads users have access to. Defaults to unlimited when unset. #[serde(skip_serializing_if = "Option::is_none")] - pub recent_threads: Option, + pub recent_threads: Option, } diff --git a/async-openai/src/types/chatkit/thread.rs b/async-openai/src/types/chatkit/thread.rs index cf2671da..20fb678a 100644 --- a/async-openai/src/types/chatkit/thread.rs +++ b/async-openai/src/types/chatkit/thread.rs @@ -9,7 +9,7 @@ pub struct ThreadResource { #[serde(default = "default_thread_object")] pub object: String, /// Unix timestamp (in seconds) for when the thread was created. - pub created_at: i64, + pub created_at: u64, /// Optional human-readable title for the thread. Defaults to null when no title has been generated. pub title: Option, /// Current status for the thread. Defaults to `active` for newly created threads. @@ -123,7 +123,7 @@ pub struct UserMessageItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Ordered content elements supplied by the user. @@ -203,7 +203,7 @@ pub struct AssistantMessageItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Ordered assistant response segments. @@ -304,7 +304,7 @@ pub struct WidgetMessageItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Serialized widget payload rendered in the UI. @@ -320,7 +320,7 @@ pub struct ClientToolCallItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Execution status for the tool call. @@ -352,7 +352,7 @@ pub struct TaskItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Subtype for the task. @@ -380,7 +380,7 @@ pub struct TaskGroupItem { #[serde(default = "default_thread_item_object")] pub object: String, /// Unix timestamp (in seconds) for when the item was created. - pub created_at: i64, + pub created_at: u64, /// Identifier of the parent thread. pub thread_id: String, /// Tasks included in the group. diff --git a/async-openai/src/types/containers/container.rs b/async-openai/src/types/containers/container.rs index b970a73e..597b4466 100644 --- a/async-openai/src/types/containers/container.rs +++ b/async-openai/src/types/containers/container.rs @@ -14,7 +14,7 @@ pub struct ContainerResource { /// Name of the container. pub name: String, /// Unix timestamp (in seconds) when the container was created. - pub created_at: u32, + pub created_at: u64, /// Status of the container (e.g., active, deleted). pub status: String, /// The container will expire after this time period. The anchor is the reference point for the expiration. @@ -23,7 +23,7 @@ pub struct ContainerResource { pub expires_after: Option, /// Unix timestamp (in seconds) when the container was last active. #[serde(skip_serializing_if = "Option::is_none")] - pub last_active_at: Option, + pub last_active_at: Option, } /// Expiration policy for containers. @@ -114,7 +114,7 @@ pub struct ContainerFileResource { /// The container this file belongs to. pub container_id: String, /// Unix timestamp (in seconds) when the file was created. - pub created_at: u32, + pub created_at: u64, /// Size of the file in bytes. pub bytes: u32, /// Path of the file in the container. diff --git a/async-openai/src/types/evals/eval.rs b/async-openai/src/types/evals/eval.rs index 96e96d5f..7fec5399 100644 --- a/async-openai/src/types/evals/eval.rs +++ b/async-openai/src/types/evals/eval.rs @@ -677,10 +677,10 @@ pub struct EvalStoredCompletionsSource { pub model: Option, /// An optional Unix timestamp to filter items created after this time. #[serde(skip_serializing_if = "Option::is_none")] - pub created_after: Option, + pub created_after: Option, /// An optional Unix timestamp to filter items created before this time. #[serde(skip_serializing_if = "Option::is_none")] - pub created_before: Option, + pub created_before: Option, /// An optional maximum number of items to return. #[serde(skip_serializing_if = "Option::is_none")] pub limit: Option, @@ -825,7 +825,7 @@ pub struct EvalRunOutputItem { /// The identifier of the evaluation group. pub eval_id: String, /// Unix timestamp (in seconds) when the evaluation run was created. - pub created_at: i64, + pub created_at: u64, /// The status of the evaluation run. pub status: String, /// The identifier for the data source item. diff --git a/async-openai/src/types/files/file.rs b/async-openai/src/types/files/file.rs index 32ea0306..c93e1cda 100644 --- a/async-openai/src/types/files/file.rs +++ b/async-openai/src/types/files/file.rs @@ -102,9 +102,9 @@ pub struct OpenAIFile { /// The size of the file in bytes. pub bytes: u32, /// The Unix timestamp (in seconds) for when the file was created. - pub created_at: u32, + pub created_at: u64, /// The Unix timestamp (in seconds) for when the file will expire. - pub expires_at: Option, + pub expires_at: Option, /// The name of the file. pub filename: String, /// The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, `fine-tune`, `fine-tune-results`, `vision`, and `user_data`. diff --git a/async-openai/src/types/finetuning/fine_tuning.rs b/async-openai/src/types/finetuning/fine_tuning.rs index 920882f3..b967bd69 100644 --- a/async-openai/src/types/finetuning/fine_tuning.rs +++ b/async-openai/src/types/finetuning/fine_tuning.rs @@ -305,7 +305,7 @@ pub struct FineTuningJob { /// The object identifier, which can be referenced in the API endpoints. pub id: String, /// The Unix timestamp (in seconds) for when the fine-tuning job was created. - pub created_at: u32, + pub created_at: u64, /// For fine-tuning jobs that have `failed`, this will contain more information on the cause of the failure. pub error: Option, /// The name of the fine-tuned model that is being created. @@ -313,7 +313,7 @@ pub struct FineTuningJob { pub fine_tuned_model: Option, // nullable: true /// The Unix timestamp (in seconds) for when the fine-tuning job was finished. /// The value will be null if the fine-tuning job is still running. - pub finished_at: Option, // nullable true + pub finished_at: Option, // nullable true /// The hyperparameters used for the fine-tuning job. This value will only be returned when running /// `supervised` jobs. @@ -394,7 +394,7 @@ pub struct FineTuningJobEvent { /// The object identifier. pub id: String, /// The Unix timestamp (in seconds) for when the fine-tuning job event was created. - pub created_at: u32, + pub created_at: u64, /// The log level of the event. pub level: Level, /// The message of the event. @@ -420,7 +420,7 @@ pub struct FineTuningJobCheckpoint { /// The checkpoint identifier, which can be referenced in the API endpoints. pub id: String, /// The Unix timestamp (in seconds) for when the checkpoint was created. - pub created_at: u32, + pub created_at: u64, /// The name of the fine-tuned checkpoint model that is created. pub fine_tuned_model_checkpoint: String, /// The step number that the checkpoint was created at. diff --git a/async-openai/src/types/images/stream.rs b/async-openai/src/types/images/stream.rs index 7b867313..749dd46d 100644 --- a/async-openai/src/types/images/stream.rs +++ b/async-openai/src/types/images/stream.rs @@ -15,7 +15,7 @@ pub struct ImageGenPartialImageEvent { /// Base64-encoded partial image data, suitable for rendering as an image. pub b64_json: String, /// The Unix timestamp when the event was created. - pub created_at: u32, + pub created_at: u64, /// The size of the requested image. pub size: ImageSize, /// The quality setting for the requested image. @@ -34,7 +34,7 @@ pub struct ImageGenCompletedEvent { /// Base64-encoded image data, suitable for rendering as an image. pub b64_json: String, /// The Unix timestamp when the event was created. - pub created_at: u32, + pub created_at: u64, /// The size of the generated image. pub size: ImageSize, /// The quality setting for the generated image. @@ -67,7 +67,7 @@ pub struct ImageEditPartialImageEvent { /// Base64-encoded partial image data, suitable for rendering as an image. pub b64_json: String, /// The Unix timestamp when the event was created. - pub created_at: u32, + pub created_at: u64, /// The size of the requested edited image. pub size: ImageSize, /// The quality setting for the requested edited image. @@ -86,7 +86,7 @@ pub struct ImageEditCompletedEvent { /// Base64-encoded final image data, suitable for rendering as an image. pub b64_json: String, /// The Unix timestamp when the event was created. - pub created_at: u32, + pub created_at: u64, /// The size of the edited image. pub size: ImageSize, /// The quality setting for the edited image. diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index 6195fbc5..b868d136 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -8,6 +8,7 @@ use crate::{ error::OpenAIError, traits::AsyncTryFrom, types::{ + assistants::CreateMessageRequestContent, audio::{ AudioInput, AudioResponseFormat, CreateSpeechResponse, CreateTranscriptionRequest, CreateTranslationRequest, TimestampGranularity, TranscriptionInclude, @@ -40,7 +41,7 @@ use crate::{ responses::EasyInputContent, uploads::AddUploadPartRequest, videos::{CreateVideoRequest, VideoSize}, - CreateMessageRequestContent, InputSource, + InputSource, }, util::{create_all_dir, create_file_part}, }; @@ -1318,6 +1319,39 @@ impl AsyncTryFrom for reqwest::multipart::Form { } } +#[cfg(feature = "realtime")] +impl AsyncTryFrom for reqwest::multipart::Form { + type Error = OpenAIError; + + async fn try_from( + request: crate::types::realtime::RealtimeCallCreateRequest, + ) -> Result { + use reqwest::multipart::Part; + + // Create SDP part with content type application/sdp + let sdp_part = Part::text(request.sdp) + .mime_str("application/sdp") + .map_err(|e| OpenAIError::InvalidArgument(format!("Invalid content type: {}", e)))?; + + let mut form = reqwest::multipart::Form::new().part("sdp", sdp_part); + + // Add session as JSON if present + if let Some(session) = request.session { + let session_json = serde_json::to_string(&session).map_err(|e| { + OpenAIError::InvalidArgument(format!("Failed to serialize session: {}", e)) + })?; + let session_part = Part::text(session_json) + .mime_str("application/json") + .map_err(|e| { + OpenAIError::InvalidArgument(format!("Invalid content type: {}", e)) + })?; + form = form.part("session", session_part); + } + + Ok(form) + } +} + // end: types to multipart form impl Default for EasyInputContent { diff --git a/async-openai/src/types/mod.rs b/async-openai/src/types/mod.rs index f5fe4b39..cb932241 100644 --- a/async-openai/src/types/mod.rs +++ b/async-openai/src/types/mod.rs @@ -1,10 +1,8 @@ //! Types used in OpenAI API requests and responses. //! These types are created from component schemas in the [OpenAPI spec](https://github.com/openai/openai-openapi) -mod assistant; -mod assistant_impls; -mod assistant_stream; +pub mod admin; +pub mod assistants; pub mod audio; -mod audit_log; pub mod batches; pub mod chat; pub mod chatkit; @@ -17,48 +15,25 @@ pub mod files; pub mod finetuning; pub mod graders; pub mod images; -mod invites; mod logprob; mod mcp; -mod message; pub mod models; pub mod moderations; -mod project_api_key; -mod project_service_account; -mod project_users; -mod projects; #[cfg_attr(docsrs, doc(cfg(feature = "realtime")))] #[cfg(feature = "realtime")] pub mod realtime; pub mod responses; -mod run; -mod step; -mod thread; pub mod uploads; -mod users; pub mod vectorstores; pub mod videos; #[cfg_attr(docsrs, doc(cfg(feature = "webhook")))] #[cfg(feature = "webhook")] pub mod webhooks; -pub use assistant::*; -pub use assistant_stream::*; -pub use audit_log::*; pub use common::*; pub use completion::*; -pub use invites::*; pub use logprob::*; pub use mcp::*; -pub use message::*; -pub use project_api_key::*; -pub use project_service_account::*; -pub use project_users::*; -pub use projects::*; -pub use run::*; -pub use step::*; -pub use thread::*; -pub use users::*; mod impls; use derive_builder::UninitializedFieldError; diff --git a/async-openai/src/types/realtime/api.rs b/async-openai/src/types/realtime/api.rs new file mode 100644 index 00000000..58c76860 --- /dev/null +++ b/async-openai/src/types/realtime/api.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::realtime::{RealtimeSessionConfiguration, Session}; + +/// Request to create a Realtime call. +#[derive(Debug, Serialize, Clone)] +pub struct RealtimeCallCreateRequest { + /// WebRTC Session Description Protocol (SDP) offer generated by the caller. + pub sdp: String, + /// Optional session configuration to apply before the realtime session is created. + #[serde(skip_serializing_if = "Option::is_none")] + pub session: Option, +} + +/// Response from creating a Realtime call. +#[derive(Debug, Clone)] +pub struct RealtimeCallCreateResponse { + /// SDP answer produced by OpenAI for the peer connection. + pub sdp: String, + /// Relative URL containing the call ID for subsequent control requests. + /// Location header value. + pub location: Option, +} + +/// Request to accept a Realtime call. +#[derive(Debug, Serialize, Clone)] +pub struct RealtimeCallAcceptRequest { + /// Realtime session configuration to apply before the caller is bridged to the model. + pub session: RealtimeSessionConfiguration, +} + +/// Request to refer a Realtime call. +#[derive(Debug, Serialize, Clone)] +pub struct RealtimeCallReferRequest { + /// URI that should appear in the SIP Refer-To header. + /// Supports values like `tel:+14155550123` or `sip:agent@example.com`. + pub target_uri: String, +} + +/// Request to reject a Realtime call. +#[derive(Debug, Serialize, Clone, Default)] +pub struct RealtimeCallRejectRequest { + /// SIP response code to send back to the caller. + /// Defaults to `603` (Decline) when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub status_code: Option, +} + +/// Request to create a Realtime client secret. +#[derive(Debug, Serialize, Clone)] +pub struct RealtimeCreateClientSecretRequest { + /// Session configuration to use for the client secret. + /// Choose either a realtime session or a transcription session. + pub session: Session, + /// Configuration for the client secret expiration. + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_after: Option, +} + +/// Client secret expiration configuration. +#[derive(Debug, Serialize, Clone)] +pub struct ClientSecretExpiration { + /// The anchor point for the client secret expiration. + /// Only `created_at` is currently supported. + #[serde(skip_serializing_if = "Option::is_none")] + pub anchor: Option, + /// The number of seconds from the anchor point to the expiration. + /// Select a value between `10` and `7200` (2 hours). + /// Defaults to 600 seconds (10 minutes) if not specified. + pub seconds: u32, +} + +/// Response from creating a Realtime client secret. +#[derive(Debug, Deserialize, Clone)] +pub struct RealtimeCreateClientSecretResponse { + /// The generated client secret value. + pub value: String, + /// Expiration timestamp for the client secret, in seconds since epoch. + pub expires_at: u64, + /// The session configuration for either a realtime or transcription session. + pub session: Session, +} diff --git a/async-openai/src/types/realtime/mod.rs b/async-openai/src/types/realtime/mod.rs index 386a92b0..d3cc8d27 100644 --- a/async-openai/src/types/realtime/mod.rs +++ b/async-openai/src/types/realtime/mod.rs @@ -1,3 +1,4 @@ +mod api; mod client_event; mod conversation_item; mod error; @@ -5,6 +6,7 @@ mod response; mod server_event; mod session; +pub use api::*; pub use client_event::*; pub use conversation_item::*; pub use error::*; diff --git a/async-openai/src/types/realtime/session.rs b/async-openai/src/types/realtime/session.rs index 66780844..1b2a3017 100644 --- a/async-openai/src/types/realtime/session.rs +++ b/async-openai/src/types/realtime/session.rs @@ -307,11 +307,24 @@ pub enum Session { RealtimeTranscriptionSession(RealtimeTranscriptionSession), } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "type")] +pub enum RealtimeSessionConfiguration { + Realtime(RealtimeSession), +} + +impl Default for RealtimeSessionConfiguration { + fn default() -> Self { + Self::Realtime(RealtimeSession::default()) + } +} + /// Realtime session object configuration. /// openapi spec type: RealtimeSessionCreateRequestGA -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct RealtimeSession { - pub audio: Audio, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio: Option