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 kind of prompt-cache breakpoint.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
85#[serde(rename_all = "lowercase")]
86pub enum CachePointType {
87    #[default]
88    Default,
89}
90
91/// A prompt-cache breakpoint (Bedrock parity). Placed in message or system
92/// content after the prefix to cache: the content part immediately preceding
93/// it gets an ephemeral `cache_control` marker on the wire; the block itself
94/// is never emitted. A cache point with no preceding content part in the same
95/// message is silently ignored.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
97pub struct CachePointBlock {
98    #[serde(rename = "type")]
99    pub r#type: CachePointType,
100}
101
102impl CachePointBlock {
103    /// The standard cache point (`{"type": "default"}`).
104    pub fn new() -> Self {
105        Self::default()
106    }
107}
108
109/// A content block that can appear in a message.
110#[derive(Debug, Clone)]
111#[non_exhaustive]
112pub enum ContentBlock {
113    Text(String),
114    Image(ImageBlock),
115    ToolUse(ToolUseBlock),
116    ToolResult(ToolResultBlock),
117    CachePoint(CachePointBlock),
118}
119
120impl ContentBlock {
121    /// Returns `Ok(&str)` if this is a `Text` block.
122    pub fn as_text(&self) -> Result<&str, &Self> {
123        match self {
124            ContentBlock::Text(s) => Ok(s.as_str()),
125            _ => Err(self),
126        }
127    }
128
129    /// Returns `Ok(&ImageBlock)` if this is an `Image` block.
130    pub fn as_image(&self) -> Result<&ImageBlock, &Self> {
131        match self {
132            ContentBlock::Image(b) => Ok(b),
133            _ => Err(self),
134        }
135    }
136
137    /// Returns `Ok(&ToolUseBlock)` if this is a `ToolUse` block.
138    pub fn as_tool_use(&self) -> Result<&ToolUseBlock, &Self> {
139        match self {
140            ContentBlock::ToolUse(b) => Ok(b),
141            _ => Err(self),
142        }
143    }
144
145    /// Returns `Ok(&ToolResultBlock)` if this is a `ToolResult` block.
146    pub fn as_tool_result(&self) -> Result<&ToolResultBlock, &Self> {
147        match self {
148            ContentBlock::ToolResult(b) => Ok(b),
149            _ => Err(self),
150        }
151    }
152
153    /// Returns `Ok(&CachePointBlock)` if this is a `CachePoint` block.
154    pub fn as_cache_point(&self) -> Result<&CachePointBlock, &Self> {
155        match self {
156            ContentBlock::CachePoint(b) => Ok(b),
157            _ => Err(self),
158        }
159    }
160}
161
162// ── Message ───────────────────────────────────────────────────────────────────
163
164/// A conversation message with a role and one or more content blocks.
165#[derive(Debug, Clone)]
166pub struct Message {
167    pub role: ConversationRole,
168    pub content: Vec<ContentBlock>,
169}
170
171impl Message {
172    pub fn builder() -> MessageBuilder {
173        MessageBuilder::default()
174    }
175
176    pub fn role(&self) -> &ConversationRole {
177        &self.role
178    }
179
180    pub fn content(&self) -> &[ContentBlock] {
181        &self.content
182    }
183}
184
185#[derive(Default)]
186pub struct MessageBuilder {
187    role: Option<ConversationRole>,
188    content: Vec<ContentBlock>,
189}
190
191impl MessageBuilder {
192    pub fn role(mut self, role: ConversationRole) -> Self {
193        self.role = Some(role);
194        self
195    }
196
197    pub fn content(mut self, block: ContentBlock) -> Self {
198        self.content.push(block);
199        self
200    }
201
202    pub fn build(self) -> Result<Message, crate::Error> {
203        let role = self
204            .role
205            .ok_or_else(|| crate::Error::client_validation("Message.role is required"))?;
206        if self.content.is_empty() {
207            return Err(crate::Error::client_validation(
208                "Message must have at least one content block",
209            ));
210        }
211        Ok(Message {
212            role,
213            content: self.content,
214        })
215    }
216}
217
218// ── System content ─────────────────────────────────────────────────────────────
219
220/// A system-prompt content entry: either text or a prompt-cache breakpoint.
221/// Construct with [`SystemContentBlock::text`] or
222/// [`SystemContentBlock::cache_point`].
223#[derive(Debug, Clone)]
224pub struct SystemContentBlock {
225    pub text: String,
226    /// When `Some`, this entry is a prompt-cache breakpoint (`text` is ignored).
227    pub cache_point: Option<CachePointBlock>,
228}
229
230impl SystemContentBlock {
231    pub fn text(text: impl Into<String>) -> Self {
232        Self {
233            text: text.into(),
234            cache_point: None,
235        }
236    }
237
238    /// A prompt-cache breakpoint marking the end of the cacheable system prefix.
239    pub fn cache_point() -> Self {
240        Self {
241            text: String::new(),
242            cache_point: Some(CachePointBlock::new()),
243        }
244    }
245}
246
247// ── Inference configuration ───────────────────────────────────────────────────
248
249#[derive(Debug, Clone, Default)]
250pub struct InferenceConfiguration {
251    pub max_tokens: Option<u32>,
252    pub temperature: Option<f64>,
253    pub top_p: Option<f64>,
254    pub stop_sequences: Option<Vec<String>>,
255}
256
257impl InferenceConfiguration {
258    pub fn builder() -> InferenceConfigurationBuilder {
259        InferenceConfigurationBuilder::default()
260    }
261}
262
263#[derive(Default)]
264pub struct InferenceConfigurationBuilder {
265    inner: InferenceConfiguration,
266}
267
268impl InferenceConfigurationBuilder {
269    pub fn max_tokens(mut self, v: u32) -> Self {
270        self.inner.max_tokens = Some(v);
271        self
272    }
273    pub fn temperature(mut self, v: f64) -> Self {
274        self.inner.temperature = Some(v);
275        self
276    }
277    pub fn top_p(mut self, v: f64) -> Self {
278        self.inner.top_p = Some(v);
279        self
280    }
281    pub fn stop_sequences(mut self, v: Vec<String>) -> Self {
282        self.inner.stop_sequences = Some(v);
283        self
284    }
285    pub fn build(self) -> InferenceConfiguration {
286        self.inner
287    }
288}
289
290// ── Tool configuration ────────────────────────────────────────────────────────
291
292#[derive(Debug, Clone)]
293pub struct ToolInputSchema {
294    pub json: Value,
295}
296
297#[derive(Debug, Clone)]
298pub struct ToolSpecification {
299    pub name: String,
300    pub description: Option<String>,
301    pub input_schema: ToolInputSchema,
302}
303
304#[derive(Debug, Clone)]
305pub struct Tool {
306    pub tool_spec: ToolSpecification,
307}
308
309#[derive(Debug, Clone)]
310pub enum ToolChoice {
311    Auto,
312    Any,
313    Tool { name: String },
314}
315
316#[derive(Debug, Clone)]
317pub struct ToolConfiguration {
318    pub tools: Vec<Tool>,
319    pub tool_choice: Option<ToolChoice>,
320}
321
322// ── Converse output ───────────────────────────────────────────────────────────
323
324/// Output wrapper — mirrors Bedrock's `ConverseOutput` enum.
325#[derive(Debug, Clone)]
326pub enum ConverseOutputEnum {
327    Message(Message),
328}
329
330impl ConverseOutputEnum {
331    /// Returns `Ok(&Message)` if this is the `Message` variant.
332    pub fn as_message(&self) -> Result<&Message, &Self> {
333        match self {
334            ConverseOutputEnum::Message(m) => Ok(m),
335            // future variants would fall through here
336        }
337    }
338}
339
340#[derive(Debug, Clone)]
341pub struct TokenUsage {
342    pub input_tokens: u32,
343    pub output_tokens: u32,
344    pub total_tokens: u32,
345    /// Prompt tokens served from the provider's prompt cache (Bedrock name).
346    pub cache_read_input_tokens: u32,
347    /// Prompt tokens written to the provider's prompt cache (Bedrock name).
348    pub cache_write_input_tokens: u32,
349}
350
351#[derive(Debug, Clone)]
352pub struct Metrics {
353    pub latency_ms: u64,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum StopReason {
358    EndTurn,
359    MaxTokens,
360    ToolUse,
361    StopSequence,
362    ContentFiltered,
363}
364
365impl StopReason {
366    pub(crate) fn from_finish_reason(s: &str) -> Self {
367        match s {
368            "stop" => StopReason::EndTurn,
369            "length" => StopReason::MaxTokens,
370            "tool_calls" | "function_call" => StopReason::ToolUse,
371            "content_filter" => StopReason::ContentFiltered,
372            _ => StopReason::EndTurn,
373        }
374    }
375}
376
377/// The top-level response from a `converse()` call.
378#[derive(Debug)]
379pub struct ConverseOutput {
380    pub(crate) output: Option<ConverseOutputEnum>,
381    pub(crate) stop_reason: StopReason,
382    pub(crate) usage: TokenUsage,
383    pub(crate) metrics: Metrics,
384}
385
386impl ConverseOutput {
387    pub fn output(&self) -> Option<&ConverseOutputEnum> {
388        self.output.as_ref()
389    }
390    pub fn stop_reason(&self) -> &StopReason {
391        &self.stop_reason
392    }
393    pub fn usage(&self) -> &TokenUsage {
394        &self.usage
395    }
396    pub fn metrics(&self) -> &Metrics {
397        &self.metrics
398    }
399}
400
401// ── Stream event types ────────────────────────────────────────────────────────
402
403#[derive(Debug, Clone)]
404pub struct MessageStartEvent {
405    pub role: ConversationRole,
406}
407
408#[derive(Debug, Clone)]
409pub struct ContentBlockStartToolUse {
410    pub tool_use_id: String,
411    pub name: String,
412}
413
414#[derive(Debug, Clone)]
415pub enum ContentBlockStartPayload {
416    ToolUse(ContentBlockStartToolUse),
417}
418
419#[derive(Debug, Clone)]
420pub struct ContentBlockStartEvent {
421    pub content_block_index: u32,
422    pub start: ContentBlockStartPayload,
423}
424
425#[derive(Debug, Clone)]
426pub enum ContentBlockDeltaPayload {
427    Text(String),
428    ToolUse { input: String },
429}
430
431#[derive(Debug, Clone)]
432pub struct ContentBlockDeltaEvent {
433    pub content_block_index: u32,
434    pub delta: ContentBlockDeltaPayload,
435}
436
437#[derive(Debug, Clone)]
438pub struct ContentBlockStopEvent {
439    pub content_block_index: u32,
440}
441
442#[derive(Debug, Clone)]
443pub struct MessageStopEvent {
444    pub stop_reason: StopReason,
445}
446
447#[derive(Debug, Clone)]
448pub struct MetadataEvent {
449    pub usage: TokenUsage,
450    pub metrics: Metrics,
451}
452
453/// All possible stream events — one variant per Bedrock stream event type.
454#[derive(Debug, Clone)]
455#[non_exhaustive]
456pub enum ConverseStreamOutput {
457    MessageStart(MessageStartEvent),
458    ContentBlockStart(ContentBlockStartEvent),
459    ContentBlockDelta(ContentBlockDeltaEvent),
460    ContentBlockStop(ContentBlockStopEvent),
461    MessageStop(MessageStopEvent),
462    Metadata(MetadataEvent),
463}