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