Skip to main content

aimux_core/
image_model.rs

1//! The `ImageModel` trait — the provider-facing interface for image generation.
2//!
3//! Aligned with Vercel AI SDK `ImageModelV4`
4//! (`reference/ai/packages/provider/src/image-model/v4/`).
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use ts_rs::TS;
9
10use crate::error::AiMuxError;
11use crate::shared::{
12    AspectRatio, SharedHeaders, SharedProviderMetadata, SharedProviderOptions, Size, Warning,
13};
14use crate::{AbortSignal, retry, timeout};
15
16/// An image file used for image editing or variation generation.
17///
18/// Aligned with V4 `ImageModelV4File`.
19#[derive(Debug, Clone, Serialize, Deserialize, TS)]
20#[ts(export)]
21pub enum ImageFile {
22    /// Inline file data (base64 or binary) with an explicit media type.
23    File {
24        /// IANA media type, e.g. `"image/png"`.
25        media_type: String,
26        /// File data as base64 string or binary bytes.
27        data: ImageFileData,
28    },
29    /// A URL pointing to the image.
30    Url {
31        /// The URL of the image file.
32        url: String,
33    },
34}
35
36/// File payload for an [`ImageFile::File`]: base64 string or raw bytes.
37#[derive(Debug, Clone, Serialize, Deserialize, TS)]
38#[ts(export)]
39pub enum ImageFileData {
40    /// Base64-encoded string.
41    Base64(String),
42    /// Raw binary bytes.
43    Binary(Vec<u8>),
44}
45
46/// The generated images, returned as a homogeneous collection.
47///
48/// The TS result type is `Array<string> | Array<Uint8Array>` — i.e. either all
49/// base64 strings or all binary blobs, never mixed. This enum models that
50/// union. Providers should return data without unnecessary conversion: if the
51/// upstream API returns base64, return [`ImageOutputs::Base64`]; if binary,
52/// return [`ImageOutputs::Binary`].
53#[derive(Debug, Clone, Serialize, Deserialize, TS)]
54#[ts(export)]
55pub enum ImageOutputs {
56    /// Images as base64-encoded strings.
57    Base64(Vec<String>),
58    /// Images as raw binary bytes.
59    Binary(Vec<Vec<u8>>),
60}
61
62/// Token usage for an image generation call (if the provider reports it).
63///
64/// Aligned with V4 `ImageModelV4Usage`.
65#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
66#[ts(export)]
67pub struct ImageUsage {
68    /// Number of input (prompt) tokens used.
69    pub input_tokens: Option<u32>,
70    /// Number of output tokens used, if reported.
71    pub output_tokens: Option<u32>,
72    /// Total tokens as reported by the provider.
73    pub total_tokens: Option<u32>,
74}
75
76/// Options passed to [`ImageModel::do_generate`].
77///
78/// Aligned with V4 `ImageModelV4CallOptions`.
79#[derive(Debug, Clone, Serialize, Deserialize, TS)]
80#[ts(export)]
81pub struct ImageCallOptions {
82    /// Prompt for the image generation. `None` for operations (e.g. upscaling)
83    /// that do not require a prompt.
84    pub prompt: Option<String>,
85
86    /// Number of images to generate.
87    pub n: u32,
88
89    /// Size of the images, in `{width}x{height}` format.
90    /// `None` uses the provider's default size.
91    pub size: Option<Size>,
92
93    /// Aspect ratio of the images, in `{width}:{height}` format.
94    /// `None` uses the provider's default aspect ratio.
95    pub aspect_ratio: Option<AspectRatio>,
96
97    /// Seed for deterministic generation. `None` uses the provider's default.
98    #[ts(type = "number | null")]
99    pub seed: Option<u64>,
100
101    /// Images for image editing or variation generation.
102    pub files: Option<Vec<ImageFile>>,
103
104    /// Mask image for inpainting operations.
105    pub mask: Option<ImageFile>,
106
107    /// Additional provider-specific options, keyed by provider name.
108    pub provider_options: SharedProviderOptions,
109
110    /// Abort signal for cancelling the operation.
111    #[serde(skip)]
112    #[ts(skip)]
113    pub abort_signal: Option<AbortSignal>,
114
115    /// Per-call retry override. `None` uses the model default.
116    pub max_retries: Option<u32>,
117
118    /// Per-call operation timeout.
119    pub timeout: Option<crate::options::TimeoutConfiguration>,
120
121    /// Additional HTTP headers to send with the request.
122    pub headers: Option<SharedHeaders>,
123}
124
125impl ImageCallOptions {
126    /// Create options with a prompt and `n = 1`, all other fields defaulted.
127    pub fn new(prompt: impl Into<String>) -> Self {
128        Self {
129            prompt: Some(prompt.into()),
130            n: 1,
131            size: None,
132            aspect_ratio: None,
133            seed: None,
134            files: None,
135            mask: None,
136            provider_options: SharedProviderOptions::new(),
137            abort_signal: None,
138            max_retries: None,
139            timeout: None,
140            headers: None,
141        }
142    }
143}
144
145/// The result of [`ImageModel::do_generate`].
146///
147/// Aligned with V4 `ImageModelV4Result`.
148#[derive(Debug, Clone, Serialize, Deserialize, TS)]
149#[ts(export)]
150pub struct ImageResult {
151    /// Generated images (base64 strings or binary data).
152    pub images: ImageOutputs,
153
154    /// Warnings for the call, e.g. unsupported features.
155    pub warnings: Vec<Warning>,
156
157    /// Additional provider-specific metadata, keyed by provider name.
158    pub provider_metadata: Option<SharedProviderMetadata>,
159
160    /// Response information for telemetry and debugging.
161    pub response: ImageResponse,
162
163    /// Optional token usage for the call (if the provider reports it).
164    pub usage: Option<ImageUsage>,
165}
166
167/// Response information for an image generation call.
168#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
169#[ts(export)]
170pub struct ImageResponse {
171    /// Timestamp for the start of the generated response (ISO 8601 string).
172    pub timestamp: Option<String>,
173    /// The ID of the model that was used to generate the response.
174    pub model_id: Option<String>,
175    /// Response headers.
176    pub headers: Option<SharedHeaders>,
177}
178
179/// The unified image generation model trait (provider-facing).
180///
181/// Aligned with V4 `ImageModelV4`.
182#[async_trait]
183pub trait ImageModel: Send + Sync {
184    /// Specification version (always `"v4"`).
185    fn specification_version(&self) -> &'static str {
186        "v4"
187    }
188
189    /// Provider name, e.g. `"openai"`.
190    fn provider(&self) -> &str;
191
192    /// Provider-specific model ID, e.g. `"dall-e-3"`.
193    fn model_id(&self) -> &str;
194
195    fn retry_config(&self) -> crate::retry::RetryConfig {
196        crate::retry::RetryConfig::default()
197    }
198
199    /// Limit of how many images can be generated in a single API call.
200    ///
201    /// `None` means there is no fixed limit. The TS spec allows this to be a
202    /// function of `modelId`; in Rust the provider resolves it to a plain
203    /// `Option<u32>`.
204    fn max_images_per_call(&self) -> Option<u32>;
205
206    /// Generate an array of images.
207    ///
208    /// Naming: the `do_` prefix prevents accidental direct usage by users.
209    async fn do_generate(&self, options: &ImageCallOptions) -> Result<ImageResult, AiMuxError>;
210}
211
212/// User-facing image generation with Core-owned retry and timeout.
213///
214/// # Errors
215///
216/// Returns the provider failure, retry exhaustion, timeout, or caller abort.
217pub async fn generate_image(
218    model: &dyn ImageModel,
219    options: ImageCallOptions,
220) -> Result<ImageResult, AiMuxError> {
221    let timeout = timeout::OperationTimeout::new(options.timeout.unwrap_or_default())?;
222    let abort_signal = options.abort_signal.clone();
223    let retries = retry::prepare_retries(
224        options.max_retries,
225        model.retry_config(),
226        abort_signal.clone(),
227    );
228    timeout::run(
229        retries.retry(|| model.do_generate(&options)),
230        abort_signal.as_ref(),
231        timeout,
232    )
233    .await
234}