diff --git a/crates/api_clients/openai_sora_client/src/error/sora_client_error.rs b/crates/api_clients/openai_sora_client/src/error/sora_client_error.rs index 56836220ea..dff286c107 100644 --- a/crates/api_clients/openai_sora_client/src/error/sora_client_error.rs +++ b/crates/api_clients/openai_sora_client/src/error/sora_client_error.rs @@ -19,7 +19,10 @@ pub enum SoraClientError { /// We haven't received a bearer token yet /// This is our own internal application state error, not something Sora returns. /// We know our client can't make the request, so we preemptively fail it. - NoBearerTokenAvailable, + NoBearerTokenForRequest, + + /// A sentinel token is not present in the client, which is required for some requests. + NoSentinelTokenForRequest, /// Issue with using the SoraCredentialBuilder. SoraCredentialBuilderError(&'static str), @@ -40,7 +43,8 @@ impl Display for SoraClientError { Self::FileForUploadHasInvalidPath => write!(f, "The file path provided for upload is invalid."), Self::LocalJwtClaimsParseError(msg) => write!(f, "Local JWT claims parse error: {}", msg), Self::MultipartFormError(err) => write!(f, "Multipart form error: {}", err), - Self::NoBearerTokenAvailable => write!(f, "No bearer token available. The client needs a bearer token to make the request."), + Self::NoBearerTokenForRequest => write!(f, "No bearer token available. The client needs a bearer token to make the request."), + Self::NoSentinelTokenForRequest => write!(f, "No sentinel token available. The client needs a sentinel token to make the request."), Self::SoraCredentialBuilderError(msg) => write!(f, "Sora Credential Builder error: {}", msg), Self::UrlParseError(err) => write!(f, "URL parse error: {}", err), Self::WreqClientError(err) => write!(f, "Wreq client error (during client creation): {}", err), diff --git a/crates/api_clients/openai_sora_client/src/error/sora_generic_api_error.rs b/crates/api_clients/openai_sora_client/src/error/sora_generic_api_error.rs index 5fcbe77f4f..a4004941b3 100644 --- a/crates/api_clients/openai_sora_client/src/error/sora_generic_api_error.rs +++ b/crates/api_clients/openai_sora_client/src/error/sora_generic_api_error.rs @@ -18,6 +18,11 @@ pub enum SoraGenericApiError { /// Includes the original body. SerdeResponseParseErrorWithBody(serde_json::Error, String), + /// serde_json::Error, likely from JSON deserialization schema mismatch. + /// Includes the original body. + /// Specifically, this is for non-200 (error) responses. + SerdeParseErrorWithBodyOnNon200(serde_json::Error, String), + /// Another error occurred. /// TODO: Try to get rid of this. UncategorizedBadResponse(String), @@ -40,6 +45,7 @@ impl Display for SoraGenericApiError { Self::CloudflareError(err) => write!(f, "Cloudflare error: {}", err), Self::Http502ErrorBadGateway(msg) => write!(f, "HTTP 502 Bad Gateway: {}", msg), Self::SerdeResponseParseErrorWithBody(err, body) => write!(f, "Failed to parse response body: {:?}. Body: {}", err, body), + Self::SerdeParseErrorWithBodyOnNon200(err, body) => write!(f, "Failed to parse non-200 response body: {:?}. Body: {}", err, body), Self::UncategorizedBadResponseWithStatusAndBody { status_code, body } => write!(f, "Uncategorized bad response: status code {}, body: {}", status_code, body), Self::UncategorizedBadResponse(msg) => write!(f, "Uncategorized bad response status: {}", msg), Self::WreqError(err) => write!(f, "Wreq client error: {}", err), diff --git a/crates/api_clients/openai_sora_client/src/error/sora_specific_api_error.rs b/crates/api_clients/openai_sora_client/src/error/sora_specific_api_error.rs index aaf0b28e4f..e2edfdb92d 100644 --- a/crates/api_clients/openai_sora_client/src/error/sora_specific_api_error.rs +++ b/crates/api_clients/openai_sora_client/src/error/sora_specific_api_error.rs @@ -7,10 +7,21 @@ pub enum SoraSpecificApiError { /// This has been generated by sending old/expired session cookies or empty cookies. FailedToGenerateBearerToken, + /// The endpoint told us we used an invalid JWT. + InvalidJwt, + + /// Sora has responded with "sentinel block". + /// This usually means we need to refresh our sentinel token. + SentinelBlockError, + /// Sora requires users to set up an account username before they can generate images. /// This requires the user to set up that username. SoraUsernameNotYetCreated, + /// Sora has responded with "token expired". + /// This (probably?) means we need to refresh our JWT bearer token. + TokenExpiredError, + /// We're sending too many tasks to Sora. /// Sora is telling us to cool it. TooManyConcurrentTasks, @@ -36,7 +47,10 @@ impl Display for SoraSpecificApiError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::FailedToGenerateBearerToken => write!(f, "Failed to generate bearer token. Probably invalid or expired session cookies."), + Self::InvalidJwt => write!(f, "Invalid JWT token. Please refresh the token."), + Self::SentinelBlockError => write!(f, "Request blocked by Sentinel Token Block. Please refresh the sentinel token."), Self::SoraUsernameNotYetCreated => write!(f, "Sora username not yet created. Please create a username in the Sora app or website."), + Self::TokenExpiredError => write!(f, "JWT token has expired. Please refresh the token."), Self::TooManyConcurrentTasks => write!(f, "Too many concurrent tasks. Please wait."), Self::UnauthorizedCookieOrBearerExpired => write!(f, "Unauthorized: cookie and/or bearer token expired"), } diff --git a/crates/api_clients/openai_sora_client/src/recipes/image_remix_with_session_auto_renew.rs b/crates/api_clients/openai_sora_client/src/recipes/image_remix_with_session_auto_renew.rs index ffa963e333..ab74fa0d07 100644 --- a/crates/api_clients/openai_sora_client/src/recipes/image_remix_with_session_auto_renew.rs +++ b/crates/api_clients/openai_sora_client/src/recipes/image_remix_with_session_auto_renew.rs @@ -11,7 +11,7 @@ use crate::requests::image_gen::{image_gen_http_request, SoraImageGenError}; use crate::requests::sentinel_refresh::generate::token::generate_token; use anyhow::anyhow; use errors::AnyhowResult; -use log::{info, warn}; +use log::{error, info, warn}; use std::time::Duration; pub struct ImageRemixAutoRenewRequest<'a> { @@ -47,36 +47,41 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq let mut refresh_sentinel = false; match err { - // We'll fail these requests... - SoraImageGenError::TooManyConcurrentTasks(_) => { - return Err(SoraSpecificApiError::TooManyConcurrentTasks.into()); - } - SoraImageGenError::UsernameRequired(_err) => { - return Err(SoraSpecificApiError::SoraUsernameNotYetCreated.into()) - } - SoraImageGenError::GenericError(err) => { - return Err(SoraGenericApiError::UncategorizedBadResponse(format!("image remix failed with GenericError: {:?}", err)).into()) - } - SoraImageGenError::NetworkError(err) => { - // TODO: The underlying type should be a reqwest::Error. - return Err(SoraGenericApiError::UncategorizedBadResponse(format!("network error: {:?}", err)).into()) + // We'll specifically fail these non-retryable requests... + + SoraError::ApiSpecific(SoraSpecificApiError::TooManyConcurrentTasks) | + SoraError::ApiSpecific(SoraSpecificApiError::SoraUsernameNotYetCreated) => { + error!("Non-retryable image generation error: {:?}", err); + return Err(err); } // We'll retry these requests... - SoraImageGenError::SentinelBlock(err) => { + SoraError::ApiSpecific(SoraSpecificApiError::SentinelBlockError) => { warn!("Image generation failed due to sentinel block error: {:?}", err); refresh_sentinel = true; } - SoraImageGenError::TokenExpired(err) => { + SoraError::ApiSpecific(SoraSpecificApiError::TokenExpiredError) => { warn!("Image generation failed due to token expired error: {:?}", err); refresh_sentinel = true; // TODO: Not sure what this error is, actually. refresh_jwt = true; } - SoraImageGenError::InvalidJwt(err) => { + SoraError::ApiSpecific(SoraSpecificApiError::InvalidJwt) => { warn!("Image generation failed due to invalid jwt error: {:?}", err); refresh_jwt = true; } + SoraError::ApiSpecific(SoraSpecificApiError::UnauthorizedCookieOrBearerExpired) => { + warn!("Image generation failed due to auth error (nb: this is a legacy error): {:?}", err); + refresh_jwt = true; + refresh_sentinel = true; + } + + // We'll fail everything else eagerly... + + _ => { + error!("Image generation error: {:?}", err); + return Err(err); + } } let mut new_bearer = None; @@ -132,18 +137,7 @@ pub async fn image_remix_with_session_auto_renew(request: ImageRemixAutoRenewReq sora_media_tokens: request.sora_media_tokens, credentials: &new_creds, request_timeout: request.request_timeout, - }).await; - - match result { - Ok(response) => Ok((response, Some(new_creds))), - Err(err) => match err { - SoraImageGenError::TooManyConcurrentTasks(err) => { - Err(SoraSpecificApiError::TooManyConcurrentTasks.into()) - } - _ => { - warn!("Image remix failed again: {:?}", err); - Err(SoraGenericApiError::UncategorizedBadResponse(format!("image remix failed again: {:?}", err)).into()) - } - } - } + }).await?; + + Ok((result, Some(new_creds))) } diff --git a/crates/api_clients/openai_sora_client/src/recipes/maybe_refresh_credentials_on_sora_error.rs b/crates/api_clients/openai_sora_client/src/recipes/maybe_refresh_credentials_on_sora_error.rs index 22f1413097..607402ef45 100644 --- a/crates/api_clients/openai_sora_client/src/recipes/maybe_refresh_credentials_on_sora_error.rs +++ b/crates/api_clients/openai_sora_client/src/recipes/maybe_refresh_credentials_on_sora_error.rs @@ -12,7 +12,7 @@ use log::{error, info, warn}; pub async fn maybe_refresh_credentials_on_sora_error(creds: &SoraCredentialSet, error: SoraError) -> Result { match error { - SoraError::Client(SoraClientError::NoBearerTokenAvailable) => { + SoraError::Client(SoraClientError::NoBearerTokenForRequest) => { error!("Previous request failed due to missing bearer token."); } SoraError::ApiSpecific(SoraSpecificApiError::UnauthorizedCookieOrBearerExpired) => { diff --git a/crates/api_clients/openai_sora_client/src/recipes/simple_image_gen_with_session_auto_renew.rs b/crates/api_clients/openai_sora_client/src/recipes/simple_image_gen_with_session_auto_renew.rs index b76bcd5d04..0ac92aec0d 100644 --- a/crates/api_clients/openai_sora_client/src/recipes/simple_image_gen_with_session_auto_renew.rs +++ b/crates/api_clients/openai_sora_client/src/recipes/simple_image_gen_with_session_auto_renew.rs @@ -46,36 +46,41 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut let mut refresh_sentinel = false; match err { - // We'll fail these requests... - SoraImageGenError::TooManyConcurrentTasks(_) => { - return Err(SoraSpecificApiError::TooManyConcurrentTasks.into()); + // We'll specifically fail these non-retryable requests... + + SoraError::ApiSpecific(SoraSpecificApiError::TooManyConcurrentTasks) | + SoraError::ApiSpecific(SoraSpecificApiError::SoraUsernameNotYetCreated) => { + error!("Non-retryable image generation error: {:?}", err); + return Err(err); } - SoraImageGenError::UsernameRequired(_err) => { - return Err(SoraSpecificApiError::SoraUsernameNotYetCreated.into()) - } - SoraImageGenError::GenericError(err) => { - return Err(SoraGenericApiError::UncategorizedBadResponse(format!("image gen failed with GenericError: {:?}", err)).into()) - } - SoraImageGenError::NetworkError(err) => { - // TODO: The underlying type should be a reqwest::Error. - return Err(SoraGenericApiError::UncategorizedBadResponse(format!("network error (todo: this should be a reqwest error): {:?}", err)).into()) - } - + // We'll retry these requests... - - SoraImageGenError::SentinelBlock(err) => { + + SoraError::ApiSpecific(SoraSpecificApiError::SentinelBlockError) => { warn!("Image generation failed due to sentinel block error: {:?}", err); refresh_sentinel = true; } - SoraImageGenError::TokenExpired(err) => { + SoraError::ApiSpecific(SoraSpecificApiError::TokenExpiredError) => { warn!("Image generation failed due to token expired error: {:?}", err); refresh_sentinel = true; // TODO: Not sure what this error is, actually. refresh_jwt = true; } - SoraImageGenError::InvalidJwt(err) => { + SoraError::ApiSpecific(SoraSpecificApiError::InvalidJwt) => { warn!("Image generation failed due to invalid jwt error: {:?}", err); refresh_jwt = true; } + SoraError::ApiSpecific(SoraSpecificApiError::UnauthorizedCookieOrBearerExpired) => { + warn!("Image generation failed due to auth error (nb: this is a legacy error): {:?}", err); + refresh_jwt = true; + refresh_sentinel = true; + } + + // We'll fail everything else eagerly... + + _ => { + error!("Image generation error: {:?}", err); + return Err(err); + } } let mut new_bearer = None; @@ -133,18 +138,7 @@ pub async fn simple_image_gen_with_session_auto_renew(request: SimpleImageGenAut image_size: request.image_size, credentials: &new_creds, request_timeout: request.request_timeout, - }).await; - - match result { - Ok(response) => Ok((response, Some(new_creds))), - Err(err) => match err { - image_gen_http_request::SoraImageGenError::TooManyConcurrentTasks(err) => { - Err(SoraSpecificApiError::TooManyConcurrentTasks.into()) - } - _ => { - warn!("Image gen failed again: {:?}", err); - Err(SoraGenericApiError::UncategorizedBadResponse(format!("image gen failed again: {:?}", err)).into()) - } - } - } + }).await?; + + Ok((result, Some(new_creds))) } diff --git a/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_http_request.rs b/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_http_request.rs index c94a36ec33..08d71e9d12 100644 --- a/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_http_request.rs +++ b/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_http_request.rs @@ -1,7 +1,11 @@ use crate::constants::user_agent::USER_AGENT; use crate::creds::sora_credential_set::SoraCredentialSet; +use crate::error::sora_client_error::SoraClientError; +use crate::error::sora_error::SoraError; +use crate::error::sora_generic_api_error::SoraGenericApiError; +use crate::error::sora_specific_api_error::SoraSpecificApiError; use crate::requests::image_gen::image_gen_status::TaskId; -use log::warn; +use log::{error, warn}; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; use thiserror::Error; @@ -156,20 +160,18 @@ pub (crate) async fn image_gen_http_request( sora_request: RawSoraImageGenRequest, credentials: &SoraCredentialSet, request_timeout: Option, -) -> Result { +) -> Result { let client = Client::new(); let cookie = credentials.cookies.to_string(); - // TODO(bt,2025-04-23): We're using a Sora payload error in place of application state error. Surface this differently. let authorization_header = credentials.jwt_bearer_token.as_ref() - .ok_or(SoraImageGenError::InvalidJwt("JWT bearer is required for image generation".to_string()))? + .ok_or(SoraClientError::NoBearerTokenForRequest)? .to_authorization_header_value(); - // TODO(bt,2025-04-23): We're using a Sora payload error in place of application state error. Surface this differently. let sentinel = credentials.sora_sentinel.as_ref() .map(|sentinel| sentinel.get_sentinel().to_string()) - .ok_or(SoraImageGenError::SentinelBlock("Sentinel is required for image generation.".to_string()))?; + .ok_or(SoraClientError::NoSentinelTokenForRequest)?; let mut http_request = client.post(SORA_IMAGE_GEN_URL) .header("User-Agent", USER_AGENT) @@ -184,52 +186,70 @@ pub (crate) async fn image_gen_http_request( } let http_request = http_request.json(&sora_request).build() - .map_err(|e| SoraImageGenError::NetworkError(e.to_string()))?; + .map_err(|err| { + error!("Error building Sora image generation HTTP request: {:?}", err); + SoraClientError::WreqClientError(err) + })?; let response = client.execute(http_request) .await - .map_err(|e| SoraImageGenError::NetworkError(e.to_string()))?; + .map_err(|err| { + error!("Error during Sora image generation request: {:?}", err); + SoraClientError::WreqClientError(err) + })?; let status = response.status(); let response_body = &response.text().await - .map_err(|e| SoraImageGenError::NetworkError(e.to_string()))?; + .map_err(|err| { + error!("Error reading Sora image generation response body: {:?}", err); + SoraClientError::WreqClientError(err) + })?; if status != StatusCode::OK { warn!("Sora image generation failure. Raw response: {:?}", response_body); let error_response: RawSoraErrorResponse = serde_json::from_str(response_body) - .map_err(|e| SoraImageGenError::GenericError(format!("Failed to parse error response: {}", e)))?; + .map_err(|err| SoraGenericApiError::SerdeParseErrorWithBodyOnNon200(err, response_body.to_string()))?; // Check for specific error codes if let Some(code) = &error_response.error.code { match code { SoraErrorCode::TokenExpired => { - return Err(SoraImageGenError::TokenExpired(error_response.error.message)); + error!("Sora token expired: {}", error_response.error); + return Err(SoraSpecificApiError::TokenExpiredError.into()); } SoraErrorCode::SentinelBlock => { - return Err(SoraImageGenError::SentinelBlock(error_response.error.message)); + error!("Sora image generation error sentinel block: {}", error_response.error); + return Err(SoraSpecificApiError::SentinelBlockError.into()); } SoraErrorCode::TooManyConcurrentTasks => { - return Err(SoraImageGenError::TooManyConcurrentTasks(error_response.error.message)); + error!("Too many concurrent tasks. Please wait."); + return Err(SoraSpecificApiError::TooManyConcurrentTasks.into()); } - SoraErrorCode::Unknown(_) => { - return Err(SoraImageGenError::GenericError(error_response.error.message)); + SoraErrorCode::Unknown(message) => { + error!("Unknown error: {} - message: {}", error_response.error.message, message); + return Err(SoraGenericApiError::UncategorizedBadResponse(error_response.error.message).into()); } SoraErrorCode::InvalidJwt => { - return Err(SoraImageGenError::InvalidJwt(error_response.error.message)); + error!("Invalid JWT token: {}", error_response.error.message); + return Err(SoraSpecificApiError::InvalidJwt.into()); } SoraErrorCode::UsernameRequired => { - return Err(SoraImageGenError::UsernameRequired(error_response.error.message)); + error!("Username required: {}", error_response.error.message); + return Err(SoraSpecificApiError::SoraUsernameNotYetCreated.into()); } } } - return Err(SoraImageGenError::GenericError(error_response.error.message)); + error!("Sora image generation failure: {}", error_response.error); + return Err(SoraGenericApiError::UncategorizedBadResponse( + format!("Unknown error code: {:?}, message: {:?}", error_response.error.code, error_response.error.message)) + .into()); } let response = serde_json::from_str(response_body) - .map_err(|e| SoraImageGenError::GenericError(format!("Failed to parse success response: {}", e)))?; + .map_err(|err| SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?; Ok(response) } diff --git a/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_status.rs b/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_status.rs index 2dbcf9ce40..29505b9a4a 100644 --- a/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_status.rs +++ b/crates/api_clients/openai_sora_client/src/requests/image_gen/image_gen_status.rs @@ -208,7 +208,7 @@ pub async fn get_image_gen_status(status_request: &StatusRequest, credentials: & Some(bearer) => bearer.to_authorization_header_value(), None => { warn!("No JWT bearer token in client - cannot fetch image gen status!"); - return Err(SoraClientError::NoBearerTokenAvailable.into()); + return Err(SoraClientError::NoBearerTokenForRequest.into()); }, }; diff --git a/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_remix.rs b/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_remix.rs index 81026c9d17..ed32075aaf 100644 --- a/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_remix.rs +++ b/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_remix.rs @@ -1,4 +1,5 @@ use crate::creds::sora_credential_set::SoraCredentialSet; +use crate::error::sora_error::SoraError; use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse}; use crate::requests::image_gen::image_gen_http_request::{image_gen_http_request, InpaintItem, InpaintItemType, OperationType, RawSoraImageGenRequest, SoraImageGenError, VideoGenType}; use log::info; @@ -15,7 +16,7 @@ pub struct SoraImageGenRemixRequest<'a> { /// The "remix" commands let you supply additional images as context. /// Sora "media tokens" of previously uploaded images must be supplied. -pub async fn sora_image_gen_remix(request: SoraImageGenRemixRequest<'_>) -> Result { +pub async fn sora_image_gen_remix(request: SoraImageGenRemixRequest<'_>) -> Result { let args = RawSoraImageGenRequest { r#type: VideoGenType::ImageGen, operation: OperationType::Remix, diff --git a/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_simple.rs b/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_simple.rs index 42ed22da78..c1e7b282a4 100644 --- a/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_simple.rs +++ b/crates/api_clients/openai_sora_client/src/requests/image_gen/sora_image_gen_simple.rs @@ -1,7 +1,7 @@ use crate::creds::sora_credential_set::SoraCredentialSet; +use crate::error::sora_error::SoraError; use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse}; use crate::requests::image_gen::image_gen_http_request::{image_gen_http_request, OperationType, RawSoraImageGenRequest, VideoGenType}; -use crate::requests::image_gen::SoraImageGenError; use std::time::Duration; pub struct SoraImageGenSimpleRequest<'a> { @@ -12,7 +12,7 @@ pub struct SoraImageGenSimpleRequest<'a> { pub request_timeout: Option, } -pub async fn sora_image_gen_simple(args: SoraImageGenSimpleRequest<'_>) -> Result { +pub async fn sora_image_gen_simple(args: SoraImageGenSimpleRequest<'_>) -> Result { let sora_request = RawSoraImageGenRequest { r#type: VideoGenType::ImageGen, operation: OperationType::SimpleCompose, @@ -24,8 +24,11 @@ pub async fn sora_image_gen_simple(args: SoraImageGenSimpleRequest<'_>) -> Resul inpaint_items: vec![], }; - // TODO: Error handling. - let result = image_gen_http_request(sora_request, args.credentials, args.request_timeout).await?; + let result = image_gen_http_request( + sora_request, + args.credentials, + args.request_timeout + ).await?; Ok(SoraImageGenResponse { task_id: result.id, diff --git a/crates/api_clients/openai_sora_client/src/requests/job/sora_job_status.rs b/crates/api_clients/openai_sora_client/src/requests/job/sora_job_status.rs index 86f838ab4c..215f31578f 100644 --- a/crates/api_clients/openai_sora_client/src/requests/job/sora_job_status.rs +++ b/crates/api_clients/openai_sora_client/src/requests/job/sora_job_status.rs @@ -17,7 +17,7 @@ pub async fn sora_job_status( let cookie = credentials.cookies.to_string(); let authorization_header = credentials.jwt_bearer_token.as_ref() - .ok_or(SoraClientError::NoBearerTokenAvailable)? + .ok_or(SoraClientError::NoBearerTokenForRequest)? .to_authorization_header_value(); let client = Client::new(); diff --git a/crates/api_clients/openai_sora_client/src/requests/list_media/list_media.rs b/crates/api_clients/openai_sora_client/src/requests/list_media/list_media.rs index 6135d76589..d29079feb8 100644 --- a/crates/api_clients/openai_sora_client/src/requests/list_media/list_media.rs +++ b/crates/api_clients/openai_sora_client/src/requests/list_media/list_media.rs @@ -42,7 +42,7 @@ pub async fn list_media(credentials: &SoraCredentialSet) -> Result