Skip to main content

systemprompt_models/artifacts/audio/
mod.rs

1//! Audio playback artifact.
2//!
3//! [`AudioArtifact`] is the renderable artifact for audio output — a source URI
4//! plus optional metadata (title, artist, artwork) and playback flags. It
5//! implements [`Artifact`] and emits its own JSON schema for tool output.
6
7use crate::artifacts::metadata::ExecutionMetadata;
8use crate::artifacts::traits::Artifact;
9use crate::artifacts::types::ArtifactType;
10use crate::execution::context::RequestContext;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_json::{Value as JsonValue, json};
14use systemprompt_identifiers::SkillId;
15
16fn default_artifact_type() -> String {
17    "audio".to_owned()
18}
19
20const fn default_true() -> bool {
21    true
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
25pub struct AudioArtifact {
26    #[serde(rename = "x-artifact-type")]
27    #[serde(default = "default_artifact_type")]
28    pub artifact_type: String,
29    pub src: String,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub mime_type: Option<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub title: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub artist: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub artwork: Option<String>,
38    #[serde(default = "default_true")]
39    pub controls: bool,
40    #[serde(default)]
41    pub autoplay: bool,
42    #[serde(default)]
43    #[serde(rename = "loop")]
44    pub loop_playback: bool,
45    #[serde(skip)]
46    #[schemars(skip)]
47    metadata: ExecutionMetadata,
48}
49
50impl AudioArtifact {
51    pub const ARTIFACT_TYPE_STR: &'static str = "audio";
52
53    pub fn new(src: impl Into<String>) -> Self {
54        Self {
55            artifact_type: "audio".to_owned(),
56            src: src.into(),
57            mime_type: None,
58            title: None,
59            artist: None,
60            artwork: None,
61            controls: true,
62            autoplay: false,
63            loop_playback: false,
64            metadata: ExecutionMetadata::default(),
65        }
66    }
67
68    pub fn with_request(mut self, ctx: &RequestContext) -> Self {
69        self.metadata = ExecutionMetadata::with_request(ctx);
70        self
71    }
72
73    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
74        self.mime_type = Some(mime_type.into());
75        self
76    }
77
78    pub fn with_title(mut self, title: impl Into<String>) -> Self {
79        self.title = Some(title.into());
80        self
81    }
82
83    pub fn with_artist(mut self, artist: impl Into<String>) -> Self {
84        self.artist = Some(artist.into());
85        self
86    }
87
88    pub fn with_artwork(mut self, artwork: impl Into<String>) -> Self {
89        self.artwork = Some(artwork.into());
90        self
91    }
92
93    pub const fn with_autoplay(mut self) -> Self {
94        self.autoplay = true;
95        self
96    }
97
98    pub const fn with_loop(mut self) -> Self {
99        self.loop_playback = true;
100        self
101    }
102
103    pub const fn without_controls(mut self) -> Self {
104        self.controls = false;
105        self
106    }
107
108    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
109        self.metadata.execution_id = Some(id.into());
110        self
111    }
112
113    pub fn with_skill(
114        mut self,
115        skill_id: impl Into<SkillId>,
116        skill_name: impl Into<String>,
117    ) -> Self {
118        self.metadata.skill_id = Some(skill_id.into());
119        self.metadata.skill_name = Some(skill_name.into());
120        self
121    }
122}
123
124impl Artifact for AudioArtifact {
125    fn artifact_type(&self) -> ArtifactType {
126        ArtifactType::Audio
127    }
128
129    fn to_schema(&self) -> JsonValue {
130        json!({
131            "type": "object",
132            "properties": {
133                "src": {
134                    "type": "string",
135                    "description": "Audio source URL or base64 data URI"
136                },
137                "mime_type": {
138                    "type": "string",
139                    "description": "MIME type (e.g., audio/mpeg)"
140                },
141                "title": {
142                    "type": "string",
143                    "description": "Track title"
144                },
145                "artist": {
146                    "type": "string",
147                    "description": "Artist name"
148                },
149                "artwork": {
150                    "type": "string",
151                    "description": "Album artwork URL"
152                },
153                "controls": {
154                    "type": "boolean",
155                    "description": "Show playback controls",
156                    "default": true
157                },
158                "autoplay": {
159                    "type": "boolean",
160                    "description": "Auto-play on load",
161                    "default": false
162                },
163                "loop": {
164                    "type": "boolean",
165                    "description": "Loop playback",
166                    "default": false
167                }
168            },
169            "required": ["src"],
170            "x-artifact-type": "audio"
171        })
172    }
173}