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 ``.
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}