use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::error::AiMuxError;
use crate::shared::{
AspectRatio, SharedHeaders, SharedProviderMetadata, SharedProviderOptions, Size, Warning,
};
use crate::{AbortSignal, retry, timeout};
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum ImageFile {
File {
media_type: String,
data: ImageFileData,
},
Url {
url: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum ImageFileData {
Base64(String),
Binary(Vec<u8>),
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub enum ImageOutputs {
Base64(Vec<String>),
Binary(Vec<Vec<u8>>),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ImageUsage {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub total_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ImageCallOptions {
pub prompt: Option<String>,
pub n: u32,
pub size: Option<Size>,
pub aspect_ratio: Option<AspectRatio>,
#[ts(type = "number | null")]
pub seed: Option<u64>,
pub files: Option<Vec<ImageFile>>,
pub mask: Option<ImageFile>,
pub provider_options: SharedProviderOptions,
#[serde(skip)]
#[ts(skip)]
pub abort_signal: Option<AbortSignal>,
pub max_retries: Option<u32>,
pub timeout: Option<crate::options::TimeoutConfiguration>,
pub headers: Option<SharedHeaders>,
}
impl ImageCallOptions {
pub fn new(prompt: impl Into<String>) -> Self {
Self {
prompt: Some(prompt.into()),
n: 1,
size: None,
aspect_ratio: None,
seed: None,
files: None,
mask: None,
provider_options: SharedProviderOptions::new(),
abort_signal: None,
max_retries: None,
timeout: None,
headers: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ImageResult {
pub images: ImageOutputs,
pub warnings: Vec<Warning>,
pub provider_metadata: Option<SharedProviderMetadata>,
pub response: ImageResponse,
pub usage: Option<ImageUsage>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct ImageResponse {
pub timestamp: Option<String>,
pub model_id: Option<String>,
pub headers: Option<SharedHeaders>,
}
#[async_trait]
pub trait ImageModel: Send + Sync {
fn specification_version(&self) -> &'static str {
"v4"
}
fn provider(&self) -> &str;
fn model_id(&self) -> &str;
fn retry_config(&self) -> crate::retry::RetryConfig {
crate::retry::RetryConfig::default()
}
fn max_images_per_call(&self) -> Option<u32>;
async fn do_generate(&self, options: &ImageCallOptions) -> Result<ImageResult, AiMuxError>;
}
pub async fn generate_image(
model: &dyn ImageModel,
options: ImageCallOptions,
) -> Result<ImageResult, AiMuxError> {
let timeout = timeout::OperationTimeout::new(options.timeout.unwrap_or_default())?;
let abort_signal = options.abort_signal.clone();
let retries = retry::prepare_retries(
options.max_retries,
model.retry_config(),
abort_signal.clone(),
);
timeout::run(
retries.retry(|| model.do_generate(&options)),
abort_signal.as_ref(),
timeout,
)
.await
}