Skip to main content

systemprompt_models/artifacts/video/
mod.rs

1//! Video artifact: a playable video reference with playback options.
2//!
3//! [`VideoArtifact`] is the builder-style producer carrying source, poster,
4//! caption, and playback flags (controls, autoplay, loop, muted).
5//!
6//! Copyright (c) systemprompt.io — Business Source License 1.1.
7//! See <https://systemprompt.io> for licensing details.
8
9use crate::artifacts::metadata::ExecutionMetadata;
10use crate::artifacts::traits::Artifact;
11use crate::artifacts::types::ArtifactType;
12use crate::execution::context::RequestContext;
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15use serde_json::{Value as JsonValue, json};
16use systemprompt_identifiers::SkillId;
17
18fn default_artifact_type() -> String {
19    "video".to_owned()
20}
21
22const fn default_true() -> bool {
23    true
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
27pub struct VideoArtifact {
28    #[serde(rename = "x-artifact-type")]
29    #[serde(default = "default_artifact_type")]
30    pub artifact_type: String,
31    pub src: String,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub mime_type: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub poster: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub caption: 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(default)]
46    pub muted: bool,
47    #[serde(skip)]
48    #[schemars(skip)]
49    metadata: ExecutionMetadata,
50}
51
52impl VideoArtifact {
53    pub const ARTIFACT_TYPE_STR: &'static str = "video";
54
55    pub fn new(src: impl Into<String>) -> Self {
56        Self {
57            artifact_type: "video".to_owned(),
58            src: src.into(),
59            mime_type: None,
60            poster: None,
61            caption: None,
62            controls: true,
63            autoplay: false,
64            loop_playback: false,
65            muted: false,
66            metadata: ExecutionMetadata::default(),
67        }
68    }
69
70    pub fn with_request(mut self, ctx: &RequestContext) -> Self {
71        self.metadata = ExecutionMetadata::with_request(ctx);
72        self
73    }
74
75    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
76        self.mime_type = Some(mime_type.into());
77        self
78    }
79
80    pub fn with_poster(mut self, poster: impl Into<String>) -> Self {
81        self.poster = Some(poster.into());
82        self
83    }
84
85    pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
86        self.caption = Some(caption.into());
87        self
88    }
89
90    pub const fn with_autoplay(mut self) -> Self {
91        self.autoplay = true;
92        self.muted = true;
93        self
94    }
95
96    pub const fn with_loop(mut self) -> Self {
97        self.loop_playback = true;
98        self
99    }
100
101    pub const fn without_controls(mut self) -> Self {
102        self.controls = false;
103        self
104    }
105
106    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
107        self.metadata.execution_id = Some(id.into());
108        self
109    }
110
111    pub fn with_skill(
112        mut self,
113        skill_id: impl Into<SkillId>,
114        skill_name: impl Into<String>,
115    ) -> Self {
116        self.metadata.skill_id = Some(skill_id.into());
117        self.metadata.skill_name = Some(skill_name.into());
118        self
119    }
120}
121
122impl Artifact for VideoArtifact {
123    fn artifact_type(&self) -> ArtifactType {
124        ArtifactType::Video
125    }
126
127    fn to_schema(&self) -> JsonValue {
128        json!({
129            "type": "object",
130            "properties": {
131                "src": {
132                    "type": "string",
133                    "description": "Video source URL or base64 data URI"
134                },
135                "mime_type": {
136                    "type": "string",
137                    "description": "MIME type (e.g., video/mp4)"
138                },
139                "poster": {
140                    "type": "string",
141                    "description": "Poster/thumbnail image URL"
142                },
143                "caption": {
144                    "type": "string",
145                    "description": "Caption displayed below the video"
146                },
147                "controls": {
148                    "type": "boolean",
149                    "description": "Show playback controls",
150                    "default": true
151                },
152                "autoplay": {
153                    "type": "boolean",
154                    "description": "Auto-play on load",
155                    "default": false
156                },
157                "loop": {
158                    "type": "boolean",
159                    "description": "Loop playback",
160                    "default": false
161                },
162                "muted": {
163                    "type": "boolean",
164                    "description": "Mute audio",
165                    "default": false
166                }
167            },
168            "required": ["src"],
169            "x-artifact-type": "video"
170        })
171    }
172}