Skip to main content

audacity_sdk/types/
mod.rs

1//! Types mirroring the Amazon Bedrock Converse SDK surface.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6// ── Conversation roles ────────────────────────────────────────────────────────
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum ConversationRole {
11    User,
12    Assistant,
13}
14
15// ── Content blocks ────────────────────────────────────────────────────────────
16
17/// A tool-use invocation block (assistant side).
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ToolUseBlock {
21    pub tool_use_id: String,
22    pub name: String,
23    pub input: Value,
24}
25
26/// A single item inside a toolResult content list.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename_all = "lowercase")]
29pub enum ToolResultContent {
30    Text(String),
31    Json(Value),
32}
33
34/// A tool-result block (user side).
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct ToolResultBlock {
38    pub tool_use_id: String,
39    pub content: Vec<ToolResultContent>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub status: Option<String>,
42}
43
44/// The format of an image content block.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "lowercase")]
47pub enum ImageFormat {
48    Png,
49    Jpeg,
50    Gif,
51    Webp,
52}
53
54impl ImageFormat {
55    /// The lowercase wire spelling, also used as the `image/<subtype>` media subtype.
56    pub fn as_str(&self) -> &'static str {
57        match self {
58            ImageFormat::Png => "png",
59            ImageFormat::Jpeg => "jpeg",
60            ImageFormat::Gif => "gif",
61            ImageFormat::Webp => "webp",
62        }
63    }
64}
65
66/// The source of an image content block.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum ImageSource {
70    /// Raw image bytes (Bedrock parity) — base64-encoded into a data URL on the wire.
71    Bytes(Vec<u8>),
72    /// An https or data URL, passed through verbatim (Audacity extension).
73    Url(String),
74}
75
76/// An image block (user side).
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct ImageBlock {
79    pub format: ImageFormat,
80    pub source: ImageSource,
81}
82
83/// The format of a video content block (Bedrock Converse `VideoFormat` parity).
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "snake_case")]
86pub enum VideoFormat {
87    Mp4,
88    Mov,
89    Mkv,
90    Webm,
91    Flv,
92    Mpeg,
93    Mpg,
94    Wmv,
95    ThreeGp,
96}
97
98impl VideoFormat {
99    /// The lowercase wire spelling of the format.
100    pub fn as_str(&self) -> &'static str {
101        match self {
102            VideoFormat::Mp4 => "mp4",
103            VideoFormat::Mov => "mov",
104            VideoFormat::Mkv => "mkv",
105            VideoFormat::Webm => "webm",
106            VideoFormat::Flv => "flv",
107            VideoFormat::Mpeg => "mpeg",
108            VideoFormat::Mpg => "mpg",
109            VideoFormat::Wmv => "wmv",
110            VideoFormat::ThreeGp => "three_gp",
111        }
112    }
113
114    /// The MIME type used on the wire (data-URL media type and `file.format`),
115    /// per the spec §3 format table.
116    pub fn mime_type(&self) -> &'static str {
117        match self {
118            VideoFormat::Mp4 => "video/mp4",
119            VideoFormat::Mov => "video/mov",
120            VideoFormat::Mkv => "video/x-matroska",
121            VideoFormat::Webm => "video/webm",
122            VideoFormat::Flv => "video/x-flv",
123            VideoFormat::Mpeg => "video/mpeg",
124            VideoFormat::Mpg => "video/mpg",
125            VideoFormat::Wmv => "video/wmv",
126            VideoFormat::ThreeGp => "video/3gpp",
127        }
128    }
129}
130
131/// The source of a video content block.
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133#[serde(rename_all = "lowercase")]
134pub enum VideoSource {
135    /// Raw video bytes (Bedrock parity) — base64-encoded into a data URL on the wire.
136    Bytes(Vec<u8>),
137    /// An `audacity://files/…` URI from the file-upload flow (spec §6), passed
138    /// through verbatim — mirrors Bedrock's `bytes` | `s3Location` pattern.
139    Uri(String),
140}
141
142/// A video block (user side, Bedrock Converse `videoBlock` parity).
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct VideoBlock {
145    pub format: VideoFormat,
146    pub source: VideoSource,
147}
148
149/// The kind of prompt-cache breakpoint.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
151#[serde(rename_all = "lowercase")]
152pub enum CachePointType {
153    #[default]
154    Default,
155}
156
157/// A prompt-cache breakpoint (Bedrock parity). Placed in message or system
158/// content after the prefix to cache: the content part immediately preceding
159/// it gets an ephemeral `cache_control` marker on the wire; the block itself
160/// is never emitted. A cache point with no preceding content part in the same
161/// message is silently ignored.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
163pub struct CachePointBlock {
164    #[serde(rename = "type")]
165    pub r#type: CachePointType,
166}
167
168impl CachePointBlock {
169    /// The standard cache point (`{"type": "default"}`).
170    pub fn new() -> Self {
171        Self::default()
172    }
173}
174
175/// A content block that can appear in a message.
176#[derive(Debug, Clone)]
177#[non_exhaustive]
178pub enum ContentBlock {
179    Text(String),
180    Image(ImageBlock),
181    Video(VideoBlock),
182    ToolUse(ToolUseBlock),
183    ToolResult(ToolResultBlock),
184    CachePoint(CachePointBlock),
185}
186
187impl ContentBlock {
188    /// Returns `Ok(&str)` if this is a `Text` block.
189    pub fn as_text(&self) -> Result<&str, &Self> {
190        match self {
191            ContentBlock::Text(s) => Ok(s.as_str()),
192            _ => Err(self),
193        }
194    }
195
196    /// Returns `Ok(&ImageBlock)` if this is an `Image` block.
197    pub fn as_image(&self) -> Result<&ImageBlock, &Self> {
198        match self {
199            ContentBlock::Image(b) => Ok(b),
200            _ => Err(self),
201        }
202    }
203
204    /// Returns `Ok(&VideoBlock)` if this is a `Video` block.
205    pub fn as_video(&self) -> Result<&VideoBlock, &Self> {
206        match self {
207            ContentBlock::Video(b) => Ok(b),
208            _ => Err(self),
209        }
210    }
211
212    /// Returns `Ok(&ToolUseBlock)` if this is a `ToolUse` block.
213    pub fn as_tool_use(&self) -> Result<&ToolUseBlock, &Self> {
214        match self {
215            ContentBlock::ToolUse(b) => Ok(b),
216            _ => Err(self),
217        }
218    }
219
220    /// Returns `Ok(&ToolResultBlock)` if this is a `ToolResult` block.
221    pub fn as_tool_result(&self) -> Result<&ToolResultBlock, &Self> {
222        match self {
223            ContentBlock::ToolResult(b) => Ok(b),
224            _ => Err(self),
225        }
226    }
227
228    /// Returns `Ok(&CachePointBlock)` if this is a `CachePoint` block.
229    pub fn as_cache_point(&self) -> Result<&CachePointBlock, &Self> {
230        match self {
231            ContentBlock::CachePoint(b) => Ok(b),
232            _ => Err(self),
233        }
234    }
235}
236
237// ── File uploads (spec §6) ────────────────────────────────────────────────────
238
239/// The upload ticket returned by `POST /v1/files` (spec §6) — also the output
240/// of the [`upload_file`](crate::Client::upload_file) helper.
241#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
242pub struct UploadFileOutput {
243    pub file_id: String,
244    /// Presigned PUT URL (~15 min expiry) the helper uploads the bytes to.
245    pub upload_url: String,
246    /// The `audacity://files/<uuid>` URI to reference in `VideoSource::Uri`.
247    pub uri: String,
248    /// When the presigned `upload_url` expires (RFC 3339).
249    pub expires_at: String,
250}
251
252// ── Image generation (spec §8) ────────────────────────────────────────────────
253
254/// One generated image: exactly one of `url` or `b64_json` is populated.
255#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
256pub struct GeneratedImage {
257    /// Signed download URL (response_format `"url"`); expires after ~24 h.
258    #[serde(default)]
259    pub url: Option<String>,
260    /// Base64-encoded image bytes (response_format `"b64_json"`).
261    #[serde(default)]
262    pub b64_json: Option<String>,
263    /// The provider's rewritten prompt, when it rewrites one.
264    #[serde(default)]
265    pub revised_prompt: Option<String>,
266}
267
268/// Token consumption for an image generation (absent for providers that do
269/// not report it).
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
271pub struct ImageGenerationUsage {
272    #[serde(default)]
273    pub input_tokens: Option<u32>,
274    #[serde(default)]
275    pub output_tokens: Option<u32>,
276    #[serde(default)]
277    pub total_tokens: Option<u32>,
278}
279
280/// The response of `POST /v1/images/generations` (spec §8) — the output of
281/// the [`generate_image`](crate::Client::generate_image) helper.
282#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
283pub struct GenerateImageOutput {
284    /// Unix timestamp (seconds) of generation.
285    #[serde(default)]
286    pub created: i64,
287    /// The generated image(s).
288    pub data: Vec<GeneratedImage>,
289    /// Token consumption, when the provider reports it.
290    #[serde(default)]
291    pub usage: Option<ImageGenerationUsage>,
292}
293
294// ── Media resolution ──────────────────────────────────────────────────────────
295
296/// Video/image token-cost knob (spec §2) — serialized as the top-level
297/// `media_resolution` request field. The gateway rewrites it into the
298/// per-part `detail` field for Gemini models (`Low` cuts video token cost
299/// ~4x); non-Gemini models ignore it. No client-side model gating.
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "snake_case")]
302pub enum MediaResolution {
303    Low,
304    Medium,
305    High,
306    UltraHigh,
307}
308
309// ── Message ───────────────────────────────────────────────────────────────────
310
311/// A conversation message with a role and one or more content blocks.
312#[derive(Debug, Clone)]
313pub struct Message {
314    pub role: ConversationRole,
315    pub content: Vec<ContentBlock>,
316}
317
318impl Message {
319    pub fn builder() -> MessageBuilder {
320        MessageBuilder::default()
321    }
322
323    pub fn role(&self) -> &ConversationRole {
324        &self.role
325    }
326
327    pub fn content(&self) -> &[ContentBlock] {
328        &self.content
329    }
330}
331
332#[derive(Default)]
333pub struct MessageBuilder {
334    role: Option<ConversationRole>,
335    content: Vec<ContentBlock>,
336}
337
338impl MessageBuilder {
339    pub fn role(mut self, role: ConversationRole) -> Self {
340        self.role = Some(role);
341        self
342    }
343
344    pub fn content(mut self, block: ContentBlock) -> Self {
345        self.content.push(block);
346        self
347    }
348
349    pub fn build(self) -> Result<Message, crate::Error> {
350        let role = self
351            .role
352            .ok_or_else(|| crate::Error::client_validation("Message.role is required"))?;
353        if self.content.is_empty() {
354            return Err(crate::Error::client_validation(
355                "Message must have at least one content block",
356            ));
357        }
358        Ok(Message {
359            role,
360            content: self.content,
361        })
362    }
363}
364
365// ── System content ─────────────────────────────────────────────────────────────
366
367/// A system-prompt content entry: either text or a prompt-cache breakpoint.
368/// Construct with [`SystemContentBlock::text`] or
369/// [`SystemContentBlock::cache_point`].
370#[derive(Debug, Clone)]
371pub struct SystemContentBlock {
372    pub text: String,
373    /// When `Some`, this entry is a prompt-cache breakpoint (`text` is ignored).
374    pub cache_point: Option<CachePointBlock>,
375}
376
377impl SystemContentBlock {
378    pub fn text(text: impl Into<String>) -> Self {
379        Self {
380            text: text.into(),
381            cache_point: None,
382        }
383    }
384
385    /// A prompt-cache breakpoint marking the end of the cacheable system prefix.
386    pub fn cache_point() -> Self {
387        Self {
388            text: String::new(),
389            cache_point: Some(CachePointBlock::new()),
390        }
391    }
392}
393
394// ── Inference configuration ───────────────────────────────────────────────────
395
396#[derive(Debug, Clone, Default)]
397pub struct InferenceConfiguration {
398    pub max_tokens: Option<u32>,
399    pub temperature: Option<f64>,
400    pub top_p: Option<f64>,
401    pub stop_sequences: Option<Vec<String>>,
402}
403
404impl InferenceConfiguration {
405    pub fn builder() -> InferenceConfigurationBuilder {
406        InferenceConfigurationBuilder::default()
407    }
408}
409
410#[derive(Default)]
411pub struct InferenceConfigurationBuilder {
412    inner: InferenceConfiguration,
413}
414
415impl InferenceConfigurationBuilder {
416    pub fn max_tokens(mut self, v: u32) -> Self {
417        self.inner.max_tokens = Some(v);
418        self
419    }
420    pub fn temperature(mut self, v: f64) -> Self {
421        self.inner.temperature = Some(v);
422        self
423    }
424    pub fn top_p(mut self, v: f64) -> Self {
425        self.inner.top_p = Some(v);
426        self
427    }
428    pub fn stop_sequences(mut self, v: Vec<String>) -> Self {
429        self.inner.stop_sequences = Some(v);
430        self
431    }
432    pub fn build(self) -> InferenceConfiguration {
433        self.inner
434    }
435}
436
437// ── Tool configuration ────────────────────────────────────────────────────────
438
439#[derive(Debug, Clone)]
440pub struct ToolInputSchema {
441    pub json: Value,
442}
443
444#[derive(Debug, Clone)]
445pub struct ToolSpecification {
446    pub name: String,
447    pub description: Option<String>,
448    pub input_schema: ToolInputSchema,
449}
450
451#[derive(Debug, Clone)]
452pub struct Tool {
453    pub tool_spec: ToolSpecification,
454}
455
456#[derive(Debug, Clone)]
457pub enum ToolChoice {
458    Auto,
459    Any,
460    Tool { name: String },
461}
462
463#[derive(Debug, Clone)]
464pub struct ToolConfiguration {
465    pub tools: Vec<Tool>,
466    pub tool_choice: Option<ToolChoice>,
467}
468
469// ── Converse output ───────────────────────────────────────────────────────────
470
471/// Output wrapper — mirrors Bedrock's `ConverseOutput` enum.
472#[derive(Debug, Clone)]
473pub enum ConverseOutputEnum {
474    Message(Message),
475}
476
477impl ConverseOutputEnum {
478    /// Returns `Ok(&Message)` if this is the `Message` variant.
479    pub fn as_message(&self) -> Result<&Message, &Self> {
480        match self {
481            ConverseOutputEnum::Message(m) => Ok(m),
482            // future variants would fall through here
483        }
484    }
485}
486
487#[derive(Debug, Clone)]
488pub struct TokenUsage {
489    pub input_tokens: u32,
490    pub output_tokens: u32,
491    pub total_tokens: u32,
492    /// Prompt tokens served from the provider's prompt cache (Bedrock name).
493    pub cache_read_input_tokens: u32,
494    /// Prompt tokens written to the provider's prompt cache (Bedrock name).
495    pub cache_write_input_tokens: u32,
496}
497
498#[derive(Debug, Clone)]
499pub struct Metrics {
500    pub latency_ms: u64,
501}
502
503#[derive(Debug, Clone, PartialEq, Eq)]
504pub enum StopReason {
505    EndTurn,
506    MaxTokens,
507    ToolUse,
508    StopSequence,
509    ContentFiltered,
510}
511
512impl StopReason {
513    pub(crate) fn from_finish_reason(s: &str) -> Self {
514        match s {
515            "stop" => StopReason::EndTurn,
516            "length" => StopReason::MaxTokens,
517            "tool_calls" | "function_call" => StopReason::ToolUse,
518            "content_filter" => StopReason::ContentFiltered,
519            _ => StopReason::EndTurn,
520        }
521    }
522}
523
524/// The top-level response from a `converse()` call.
525#[derive(Debug)]
526pub struct ConverseOutput {
527    pub(crate) output: Option<ConverseOutputEnum>,
528    pub(crate) stop_reason: StopReason,
529    pub(crate) usage: TokenUsage,
530    pub(crate) metrics: Metrics,
531}
532
533impl ConverseOutput {
534    pub fn output(&self) -> Option<&ConverseOutputEnum> {
535        self.output.as_ref()
536    }
537    pub fn stop_reason(&self) -> &StopReason {
538        &self.stop_reason
539    }
540    pub fn usage(&self) -> &TokenUsage {
541        &self.usage
542    }
543    pub fn metrics(&self) -> &Metrics {
544        &self.metrics
545    }
546}
547
548// ── Stream event types ────────────────────────────────────────────────────────
549
550#[derive(Debug, Clone)]
551pub struct MessageStartEvent {
552    pub role: ConversationRole,
553}
554
555#[derive(Debug, Clone)]
556pub struct ContentBlockStartToolUse {
557    pub tool_use_id: String,
558    pub name: String,
559}
560
561#[derive(Debug, Clone)]
562pub enum ContentBlockStartPayload {
563    ToolUse(ContentBlockStartToolUse),
564}
565
566#[derive(Debug, Clone)]
567pub struct ContentBlockStartEvent {
568    pub content_block_index: u32,
569    pub start: ContentBlockStartPayload,
570}
571
572#[derive(Debug, Clone)]
573pub enum ContentBlockDeltaPayload {
574    Text(String),
575    ToolUse { input: String },
576}
577
578#[derive(Debug, Clone)]
579pub struct ContentBlockDeltaEvent {
580    pub content_block_index: u32,
581    pub delta: ContentBlockDeltaPayload,
582}
583
584#[derive(Debug, Clone)]
585pub struct ContentBlockStopEvent {
586    pub content_block_index: u32,
587}
588
589#[derive(Debug, Clone)]
590pub struct MessageStopEvent {
591    pub stop_reason: StopReason,
592}
593
594#[derive(Debug, Clone)]
595pub struct MetadataEvent {
596    pub usage: TokenUsage,
597    pub metrics: Metrics,
598}
599
600/// All possible stream events — one variant per Bedrock stream event type.
601#[derive(Debug, Clone)]
602#[non_exhaustive]
603pub enum ConverseStreamOutput {
604    MessageStart(MessageStartEvent),
605    ContentBlockStart(ContentBlockStartEvent),
606    ContentBlockDelta(ContentBlockDeltaEvent),
607    ContentBlockStop(ContentBlockStopEvent),
608    MessageStop(MessageStopEvent),
609    Metadata(MetadataEvent),
610}