Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3b31236
from traits impls
64bit Nov 20, 2025
8f845bb
updated examples/responses to be simplified
64bit Nov 20, 2025
2dd898d
updates
64bit Nov 20, 2025
9c37df6
more from traits
64bit Nov 20, 2025
2b4a658
updated images-and-vision example
64bit Nov 20, 2025
29c7850
further from traits
64bit Nov 20, 2025
cc89a7c
add responses-structured-outputs example
64bit Nov 20, 2025
7617e80
add streaming example
64bit Nov 20, 2025
5bd23f8
responses stream event ergonomics: event_type
64bit Nov 20, 2025
34006a6
updated responses function to include streaming example
64bit Nov 20, 2025
55e9f1a
handle StreamEnded gracefully
64bit Nov 20, 2025
221856a
update example now that StreamEnded is handled gracefully
64bit Nov 20, 2025
57eee59
fix
64bit Nov 20, 2025
3dc3278
fix: publish false
64bit Nov 20, 2025
275412f
remove use of deprecated field types
64bit Nov 21, 2025
64dbfb2
remove examples using deprecated types/fields
64bit Nov 21, 2025
add4a26
responses refactor
64bit Nov 21, 2025
11cb67f
fix coversation example
64bit Nov 21, 2025
081f83a
refactor impls
64bit Nov 21, 2025
7c99322
move warpper types impls in its own file
64bit Nov 21, 2025
a4ca081
refactor
64bit Nov 21, 2025
33835f7
image impls
64bit Nov 21, 2025
5663132
impls and forms for each type mods
64bit Nov 21, 2025
8f80c5b
create sdk.rs which add SDK like functionality on types
64bit Nov 21, 2025
40b2f47
event types for realtime events
64bit Nov 21, 2025
8ee307e
update example to use event_type()
64bit Nov 21, 2025
eaf5f87
Event type for assistant stream events
64bit Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions async-openai/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,18 @@ where
while let Some(ev) = event_source.next().await {
match ev {
Err(e) => {
if let Err(_e) = tx.send(Err(map_stream_error(e).await)) {
// rx dropped
break;
// Handle StreamEnded gracefully - it's a normal end of stream, not an error
// https://github.com/64bit/async-openai/issues/456
match &e {
EventSourceError::StreamEnded => {
break;
}
_ => {
if let Err(_e) = tx.send(Err(map_stream_error(e).await)) {
// rx dropped
break;
}
}
}
}
Ok(event) => match event {
Expand Down Expand Up @@ -664,9 +673,18 @@ where
while let Some(ev) = event_source.next().await {
match ev {
Err(e) => {
if let Err(_e) = tx.send(Err(map_stream_error(e).await)) {
// rx dropped
break;
// Handle StreamEnded gracefully - it's a normal end of stream, not an error
// https://github.com/64bit/async-openai/issues/456
match &e {
EventSourceError::StreamEnded => {
break;
}
_ => {
if let Err(_e) = tx.send(Err(map_stream_error(e).await)) {
// rx dropped
break;
}
}
}
}
Ok(event) => match event {
Expand Down
133 changes: 133 additions & 0 deletions async-openai/src/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use crate::{
admin_api_keys::AdminAPIKeys,
assistants::Assistants,
audio::Audio,
audit_logs::AuditLogs,
batches::Batches,
certificates::Certificates,
chat::Chat,
chatkit::{Chatkit, ChatkitSessions, ChatkitThreads},
completion::Completions,
container_files::ContainerFiles,
containers::Containers,
conversation_items::ConversationItems,
conversations::Conversations,
embedding::Embeddings,
eval_run_output_items::EvalRunOutputItems,
eval_runs::EvalRuns,
evals::Evals,
file::Files,
fine_tuning::FineTuning,
group_roles::GroupRoles,
group_users::GroupUsers,
groups::Groups,
image::Images,
invites::Invites,
messages::Messages,
model::Models,
moderation::Moderations,
project_api_keys::ProjectAPIKeys,
project_certificates::ProjectCertificates,
project_group_roles::ProjectGroupRoles,
project_groups::ProjectGroups,
project_rate_limits::ProjectRateLimits,
project_roles::ProjectRoles,
project_service_accounts::ProjectServiceAccounts,
project_user_roles::ProjectUserRoles,
project_users::ProjectUsers,
projects::Projects,
responses::Responses,
roles::Roles,
runs::Runs,
speech::Speech,
steps::Steps,
threads::Threads,
transcriptions::Transcriptions,
translations::Translations,
uploads::Uploads,
usage::Usage,
user_roles::UserRoles,
users::Users,
vector_store_file_batches::VectorStoreFileBatches,
vector_store_files::VectorStoreFiles,
vector_stores::VectorStores,
video::Videos,
};

// request builder impls macro

/// Macro to implement `RequestOptionsBuilder` for wrapper types containing `RequestOptions`
macro_rules! impl_request_options_builder {
($type:ident) => {
impl<'c, C: crate::config::Config> crate::traits::RequestOptionsBuilder for $type<'c, C> {
fn options_mut(&mut self) -> &mut crate::RequestOptions {
&mut self.request_options
}

fn options(&self) -> &crate::RequestOptions {
&self.request_options
}
}
};
}

#[cfg(feature = "realtime")]
use crate::Realtime;

impl_request_options_builder!(AdminAPIKeys);
impl_request_options_builder!(Assistants);
impl_request_options_builder!(Audio);
impl_request_options_builder!(AuditLogs);
impl_request_options_builder!(Batches);
impl_request_options_builder!(Certificates);
impl_request_options_builder!(Chat);
impl_request_options_builder!(Chatkit);
impl_request_options_builder!(ChatkitSessions);
impl_request_options_builder!(ChatkitThreads);
impl_request_options_builder!(Completions);
impl_request_options_builder!(ContainerFiles);
impl_request_options_builder!(Containers);
impl_request_options_builder!(ConversationItems);
impl_request_options_builder!(Conversations);
impl_request_options_builder!(Embeddings);
impl_request_options_builder!(Evals);
impl_request_options_builder!(EvalRunOutputItems);
impl_request_options_builder!(EvalRuns);
impl_request_options_builder!(Files);
impl_request_options_builder!(FineTuning);
impl_request_options_builder!(GroupRoles);
impl_request_options_builder!(GroupUsers);
impl_request_options_builder!(Groups);
impl_request_options_builder!(Images);
impl_request_options_builder!(Invites);
impl_request_options_builder!(Messages);
impl_request_options_builder!(Models);
impl_request_options_builder!(Moderations);
impl_request_options_builder!(Projects);
impl_request_options_builder!(ProjectGroupRoles);
impl_request_options_builder!(ProjectGroups);
impl_request_options_builder!(ProjectRoles);
impl_request_options_builder!(ProjectUserRoles);
impl_request_options_builder!(ProjectUsers);
impl_request_options_builder!(ProjectServiceAccounts);
impl_request_options_builder!(ProjectAPIKeys);
impl_request_options_builder!(ProjectRateLimits);
impl_request_options_builder!(ProjectCertificates);
impl_request_options_builder!(Roles);
#[cfg(feature = "realtime")]
impl_request_options_builder!(Realtime);
impl_request_options_builder!(Responses);
impl_request_options_builder!(Runs);
impl_request_options_builder!(Speech);
impl_request_options_builder!(Steps);
impl_request_options_builder!(Threads);
impl_request_options_builder!(Transcriptions);
impl_request_options_builder!(Translations);
impl_request_options_builder!(Uploads);
impl_request_options_builder!(Usage);
impl_request_options_builder!(UserRoles);
impl_request_options_builder!(Users);
impl_request_options_builder!(VectorStoreFileBatches);
impl_request_options_builder!(VectorStoreFiles);
impl_request_options_builder!(VectorStores);
impl_request_options_builder!(Videos);
1 change: 1 addition & 0 deletions async-openai/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ mod group_roles;
mod group_users;
mod groups;
mod image;
mod impls;
mod invites;
mod messages;
mod model;
Expand Down
34 changes: 33 additions & 1 deletion async-openai/src/types/assistants/assistant_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use futures::Stream;
use serde::Deserialize;

use crate::error::{map_deserialization_error, ApiError, OpenAIError, StreamError};

use crate::traits::EventType;
use crate::types::assistants::{
MessageDeltaObject, MessageObject, RunObject, RunStepDeltaObject, RunStepObject, ThreadObject,
};
Expand Down Expand Up @@ -213,3 +213,35 @@ impl TryFrom<eventsource_stream::Event> for AssistantStreamEvent {
}
}
}

impl EventType for AssistantStreamEvent {
fn event_type(&self) -> &'static str {
match self {
AssistantStreamEvent::ThreadCreated(_) => "thread.created",
AssistantStreamEvent::ThreadRunCreated(_) => "thread.run.created",
AssistantStreamEvent::ThreadRunQueued(_) => "thread.run.queued",
AssistantStreamEvent::ThreadRunInProgress(_) => "thread.run.in_progress",
AssistantStreamEvent::ThreadRunRequiresAction(_) => "thread.run.requires_action",
AssistantStreamEvent::ThreadRunCompleted(_) => "thread.run.completed",
AssistantStreamEvent::ThreadRunIncomplete(_) => "thread.run.incomplete",
AssistantStreamEvent::ThreadRunFailed(_) => "thread.run.failed",
AssistantStreamEvent::ThreadRunCancelling(_) => "thread.run.cancelling",
AssistantStreamEvent::ThreadRunCancelled(_) => "thread.run.cancelled",
AssistantStreamEvent::ThreadRunExpired(_) => "thread.run.expired",
AssistantStreamEvent::ThreadRunStepCreated(_) => "thread.run.step.created",
AssistantStreamEvent::ThreadRunStepInProgress(_) => "thread.run.step.in_progress",
AssistantStreamEvent::ThreadRunStepDelta(_) => "thread.run.step.delta",
AssistantStreamEvent::ThreadRunStepCompleted(_) => "thread.run.step.completed",
AssistantStreamEvent::ThreadRunStepFailed(_) => "thread.run.step.failed",
AssistantStreamEvent::ThreadRunStepCancelled(_) => "thread.run.step.cancelled",
AssistantStreamEvent::ThreadRunStepExpired(_) => "thread.run.step.expired",
AssistantStreamEvent::ThreadMessageCreated(_) => "thread.message.created",
AssistantStreamEvent::ThreadMessageInProgress(_) => "thread.message.in_progress",
AssistantStreamEvent::ThreadMessageDelta(_) => "thread.message.delta",
AssistantStreamEvent::ThreadMessageCompleted(_) => "thread.message.completed",
AssistantStreamEvent::ThreadMessageIncomplete(_) => "thread.message.incomplete",
AssistantStreamEvent::ErrorEvent(_) => "error",
AssistantStreamEvent::Done(_) => "done",
}
}
}
19 changes: 19 additions & 0 deletions async-openai/src/types/assistants/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::types::assistants::CreateMessageRequestContent;

impl From<String> for CreateMessageRequestContent {
fn from(value: String) -> Self {
Self::Content(value)
}
}

impl From<&str> for CreateMessageRequestContent {
fn from(value: &str) -> Self {
Self::Content(value.to_string())
}
}

impl Default for CreateMessageRequestContent {
fn default() -> Self {
Self::Content("".into())
}
}
1 change: 1 addition & 0 deletions async-openai/src/types/assistants/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod api;
mod assistant;
mod assistant_impls;
mod assistant_stream;
mod impls;
mod message;
mod run;
mod step;
Expand Down
105 changes: 105 additions & 0 deletions async-openai/src/types/audio/form.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use crate::{
error::OpenAIError,
traits::AsyncTryFrom,
types::audio::{
CreateTranscriptionRequest, CreateTranslationRequest, TranscriptionChunkingStrategy,
},
util::create_file_part,
};

impl AsyncTryFrom<CreateTranscriptionRequest> for reqwest::multipart::Form {
type Error = OpenAIError;

async fn try_from(request: CreateTranscriptionRequest) -> Result<Self, Self::Error> {
let audio_part = create_file_part(request.file.source).await?;

let mut form = reqwest::multipart::Form::new()
.part("file", audio_part)
.text("model", request.model);

if let Some(language) = request.language {
form = form.text("language", language);
}

if let Some(prompt) = request.prompt {
form = form.text("prompt", prompt);
}

if let Some(response_format) = request.response_format {
form = form.text("response_format", response_format.to_string())
}

if let Some(temperature) = request.temperature {
form = form.text("temperature", temperature.to_string())
}

if let Some(include) = request.include {
for inc in include {
form = form.text("include[]", inc.to_string());
}
}

if let Some(timestamp_granularities) = request.timestamp_granularities {
for tg in timestamp_granularities {
form = form.text("timestamp_granularities[]", tg.to_string());
}
}

if let Some(stream) = request.stream {
form = form.text("stream", stream.to_string());
}

if let Some(chunking_strategy) = request.chunking_strategy {
match chunking_strategy {
TranscriptionChunkingStrategy::Auto => {
form = form.text("chunking_strategy", "auto");
}
TranscriptionChunkingStrategy::ServerVad(vad_config) => {
form = form.text(
"chunking_strategy",
serde_json::to_string(&vad_config).unwrap().to_string(),
);
}
}
}

if let Some(known_speaker_names) = request.known_speaker_names {
for kn in known_speaker_names {
form = form.text("known_speaker_names[]", kn.to_string());
}
}

if let Some(known_speaker_references) = request.known_speaker_references {
for kn in known_speaker_references {
form = form.text("known_speaker_references[]", kn.to_string());
}
}

Ok(form)
}
}

impl AsyncTryFrom<CreateTranslationRequest> for reqwest::multipart::Form {
type Error = OpenAIError;

async fn try_from(request: CreateTranslationRequest) -> Result<Self, Self::Error> {
let audio_part = create_file_part(request.file.source).await?;

let mut form = reqwest::multipart::Form::new()
.part("file", audio_part)
.text("model", request.model);

if let Some(prompt) = request.prompt {
form = form.text("prompt", prompt);
}

if let Some(response_format) = request.response_format {
form = form.text("response_format", response_format.to_string())
}

if let Some(temperature) = request.temperature {
form = form.text("temperature", temperature.to_string())
}
Ok(form)
}
}
Loading
Loading