Skip to main content

oxios_kernel/image_gen/
mod.rs

1//! Image generation provider clients.
2//!
3//! `oxi-sdk` 0.58.0 has no image generation capability — the `Provider`
4//! trait offers only `stream()`/`name()`, and oxi-ai's
5//! `ImageGenerationRequest`/`ImageGenerationResponse` types are dead code
6//! (defined, never implemented, not re-exported). This module is Oxios's own
7//! image-gen backend, built directly on `reqwest`.
8//!
9//! Type shapes align with the oxi-ai dead types (`types.rs`) so a future
10//! oxi-sdk implementation can replace this module with minimal friction.
11//! Unlike oxi-ai (which returns `Vec<Vec<u8>>` bytes), Oxios always normalizes
12//! results to URLs — agents consume images as markdown `![](url)`.
13
14mod fal;
15mod openai;
16mod store;
17mod types;
18
19use async_trait::async_trait;
20
21pub(crate) use fal::FAL_DEFAULT_BASE;
22pub use fal::FalImageProvider;
23pub use openai::OpenAiImageProvider;
24pub use store::{FsImageStore, ImageSink};
25pub use types::{GeneratedImage, ImageGenRequest, ImageGenResult, ImageSize};
26
27/// Errors from image generation.
28#[derive(Debug, thiserror::Error)]
29pub enum ImageGenError {
30    /// The request did not specify a model (the caller must resolve a default).
31    #[error("image generation requires a model")]
32    MissingModel,
33    /// The provider response had no usable image (neither `url` nor `b64_json`).
34    #[error("bad image response: {0}")]
35    BadResponse(String),
36    /// The provider returned a non-success HTTP status.
37    #[error("provider error ({status}): {body}")]
38    Http {
39        /// HTTP status code.
40        status: u16,
41        /// Response body (truncated by the provider).
42        body: String,
43    },
44    /// A network/transport error.
45    #[error("transport error: {0}")]
46    Transport(String),
47    /// Failed to persist image bytes locally.
48    #[error("image store error: {0}")]
49    Store(String),
50    /// Base64 decoding of a `b64_json` response failed.
51    #[error("base64 decode error: {0}")]
52    Base64(String),
53    /// Polling timed out before the provider returned a result (async providers).
54    #[error("operation timed out: {context}")]
55    Timeout {
56        /// Context describing what timed out.
57        context: String,
58    },
59}
60
61/// A text-to-image provider.
62///
63/// Implementations resolve their own credentials and base URL at construction
64/// time (see [`OpenAiImageProvider::new`]). Callers inject an already-resolved
65/// `(base_url, api_key, image_dir)` so the module stays unit-testable with
66/// mocks — credential resolution is the tool layer's job.
67#[async_trait]
68pub trait ImageGenProvider: Send + Sync {
69    /// Provider identifier (e.g. `"openai"`).
70    fn name(&self) -> &'static str;
71
72    /// Generate images. The result's `images[].url` are always fetchable URLs
73    /// — b64-only providers persist locally and return a served URL.
74    ///
75    /// `req.model` MUST be set; the caller resolves a config default first.
76    async fn generate(&self, req: &ImageGenRequest) -> Result<ImageGenResult, ImageGenError>;
77}