Skip to main content

aimux_core/
shared.rs

1//! Shared V4 value types used across all non-chat model traits.
2//!
3//! Aligned with Vercel AI SDK `SharedV4*` types
4//! (`reference/ai/packages/provider/src/shared/v4/`).
5//!
6//! These types are the common currency passed between the user-facing API and
7//! the provider-facing traits (`EmbeddingModel`, `ImageModel`, …). They are
8//! deliberately opaque to the core: providers read/write provider-specific
9//! keys inside them.
10
11use std::collections::HashMap;
12
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15use ts_rs::TS;
16
17// Keep the established shared-module paths while the canonical cancellation
18// implementation lives in its focused module.
19pub use crate::abort_signal::AbortSignal;
20pub use crate::types::Warning;
21
22/// Additional HTTP headers sent with a provider request.
23///
24/// Aligned with V4 `SharedV4Headers` (`Record<string, string>`).
25pub type SharedHeaders = HashMap<String, String>;
26
27/// Additional provider-specific options (inputs), keyed by provider name.
28///
29/// The inner value is a JSON object whose keys are provider-specific option
30/// names. Aligned with V4 `SharedV4ProviderOptions`
31/// (`Record<string, JSONObject>`).
32///
33/// ```json
34/// { "anthropic": { "cacheControl": { "type": "ephemeral" } } }
35/// ```
36pub type SharedProviderOptions = HashMap<String, Value>;
37
38/// Additional provider-specific metadata (outputs), keyed by provider name.
39///
40/// Aligned with V4 `SharedV4ProviderMetadata` (`Record<string, JSONObject>`).
41pub type SharedProviderMetadata = HashMap<String, Value>;
42
43/// A mapping of provider names to provider-specific file identifiers.
44///
45/// Allows a logical file to be referenced across providers without
46/// re-uploading. Aligned with V4 `SharedV4ProviderReference`.
47///
48/// ```json
49/// { "openai": "file-abc123", "anthropic": "file-xyz789" }
50/// ```
51pub type SharedProviderReference = HashMap<String, String>;
52
53/// Either raw bytes or a base64-encoded string.
54///
55/// Providers should pass file data through without unnecessary conversion: if
56/// the upstream API returns base64, return [`FileBytes::Base64`]; if it
57/// returns binary, return [`FileBytes::Binary`].
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
59#[ts(export)]
60pub enum FileBytes {
61    /// Raw binary bytes.
62    Binary(Vec<u8>),
63    /// A base64-encoded string.
64    Base64(String),
65}
66
67/// File data as a tagged discriminated union.
68///
69/// Aligned with V4 `SharedV4FileData`.
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
71#[ts(export)]
72pub enum FileData {
73    /// Raw bytes (`Uint8Array`) or a base64-encoded string.
74    Data { data: FileBytes },
75    /// A URL that points to the file.
76    Url { url: String },
77    /// A provider reference (`{ [provider]: id }`).
78    Reference { reference: SharedProviderReference },
79    /// Inline text content (e.g. an inline text document).
80    Text { text: String },
81}
82
83/// Image/video size in `{width}x{height}` format (e.g. `"1024x1024"`).
84///
85/// Newtype that enforces the `WxH` format used by V4 image `size` and video
86/// `resolution` options. Aligned with the TS template-literal type
87/// `` `${number}x${number}` ``.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
89#[ts(export)]
90pub struct Size {
91    width: u32,
92    height: u32,
93}
94
95impl Size {
96    /// Create a size from explicit pixel dimensions.
97    #[must_use]
98    pub const fn new(width: u32, height: u32) -> Self {
99        Self { width, height }
100    }
101
102    /// Parse a `{width}x{height}` string (e.g. `"1280x720"`).
103    ///
104    /// Returns an error if the string is not in the expected format or either
105    /// dimension is zero.
106    ///
107    /// # Errors
108    ///
109    /// Returns a `String` describing the malformed input when it is not
110    /// `{width}x{height}` or either dimension is zero.
111    pub fn parse(s: &str) -> Result<Self, String> {
112        let (w, h) = parse_pair(s, 'x').ok_or_else(|| {
113            format!("invalid size `{s}`: expected format `{{width}}x{{height}}` (e.g. `1024x1024`)")
114        })?;
115        if w == 0 || h == 0 {
116            return Err(format!("invalid size `{s}`: width and height must be > 0"));
117        }
118        Ok(Self::new(w, h))
119    }
120
121    /// Width in pixels.
122    #[must_use]
123    pub const fn width(&self) -> u32 {
124        self.width
125    }
126
127    /// Height in pixels.
128    #[must_use]
129    pub const fn height(&self) -> u32 {
130        self.height
131    }
132}
133
134impl std::fmt::Display for Size {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "{}x{}", self.width, self.height)
137    }
138}
139
140impl std::str::FromStr for Size {
141    type Err = String;
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        Self::parse(s)
144    }
145}
146
147/// Aspect ratio in `{width}:{height}` format (e.g. `"16:9"`).
148///
149/// Newtype that enforces the `W:H` format used by V4 image and video
150/// `aspectRatio` options. Aligned with the TS template-literal type
151/// `` `${number}:${number}` ``.
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
153#[ts(export)]
154pub struct AspectRatio {
155    width: u32,
156    height: u32,
157}
158
159impl AspectRatio {
160    /// Create an aspect ratio from explicit components.
161    #[must_use]
162    pub const fn new(width: u32, height: u32) -> Self {
163        Self { width, height }
164    }
165
166    /// Parse a `{width}:{height}` string (e.g. `"16:9"`).
167    ///
168    /// # Errors
169    ///
170    /// Returns a `String` describing the malformed input when it is not
171    /// `{width}:{height}` or either value is zero.
172    pub fn parse(s: &str) -> Result<Self, String> {
173        let (w, h) = parse_pair(s, ':').ok_or_else(|| {
174            format!(
175                "invalid aspect ratio `{s}`: expected format `{{width}}:{{height}}` (e.g. `16:9`)"
176            )
177        })?;
178        if w == 0 || h == 0 {
179            return Err(format!(
180                "invalid aspect ratio `{s}`: width and height must be > 0"
181            ));
182        }
183        Ok(Self::new(w, h))
184    }
185
186    /// Width component.
187    #[must_use]
188    pub const fn width(&self) -> u32 {
189        self.width
190    }
191
192    /// Height component.
193    #[must_use]
194    pub const fn height(&self) -> u32 {
195        self.height
196    }
197}
198
199impl std::fmt::Display for AspectRatio {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "{}:{}", self.width, self.height)
202    }
203}
204
205impl std::str::FromStr for AspectRatio {
206    type Err = String;
207    fn from_str(s: &str) -> Result<Self, Self::Err> {
208        Self::parse(s)
209    }
210}
211
212/// Parse a `{a}{sep}{b}` pair of unsigned integers.
213fn parse_pair(s: &str, sep: char) -> Option<(u32, u32)> {
214    let (a, b) = s.split_once(sep)?;
215    let a: u32 = a.trim().parse().ok()?;
216    let b: u32 = b.trim().parse().ok()?;
217    Some((a, b))
218}
219
220/// Response information for telemetry and debugging.
221///
222/// Aligned with the `response` object that appears on most V4 model results.
223/// All fields are optional because different providers populate different
224/// subsets; traits whose TS spec marks `response` as required use
225/// `ResponseInfo` directly, while those that mark it optional use
226/// `Option<ResponseInfo>`.
227#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
228#[ts(export)]
229pub struct ResponseInfo {
230    /// Timestamp for the start of the generated response (ISO 8601 string).
231    pub timestamp: Option<String>,
232    /// The ID of the model that was used to generate the response.
233    pub model_id: Option<String>,
234    /// Response headers.
235    pub headers: Option<SharedHeaders>,
236    /// The response body (opaque JSON).
237    pub body: Option<Value>,
238}
239
240/// Optional request information for telemetry and debugging.
241///
242/// Aligned with the `request?` object that appears on some V4 model results.
243#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
244#[ts(export)]
245pub struct RequestInfo {
246    /// The request body that was sent (opaque JSON).
247    pub body: Option<Value>,
248}