everruns_integrations_sprites/
lib.rs1pub mod client;
13pub mod connection;
14pub mod state;
15mod tools;
16
17use everruns_core::LEASED_RESOURCES_FEATURE;
18use everruns_core::capabilities::{
19 Capability, CapabilityLocalization, CapabilityStatus, IntegrationPlugin, RiskLevel,
20};
21use everruns_core::connector::ConnectorPlugin;
22use everruns_core::tools::Tool;
23
24use std::sync::LazyLock;
25
26use connection::SpritesConnector;
27
28use tools::{
29 SpritesCheckpointTool, SpritesCreateSpriteTool, SpritesExecTool, SpritesListSpritesTool,
30 SpritesManageSpriteTool, SpritesReadFileTool, SpritesRestoreCheckpointTool,
31 SpritesServiceUrlTool, SpritesWriteFileTool,
32};
33
34inventory::submit! {
39 IntegrationPlugin {
40 experimental_only: false,
41 feature_flag: None,
42 factory: || Box::new(SpritesCapability),
43 }
44}
45
46inventory::submit! {
47 ConnectorPlugin {
48 experimental_only: true,
49 factory: || Box::new(SpritesConnector),
50 }
51}
52
53const SPRITES_API_BASE: &str = "https://api.sprites.dev/v1";
58const SPRITES_SECRET_PREFIX: &str = "sprites_sprite:";
59const EXEC_TIMEOUT_MS: u64 = 120_000;
60const SPRITES_WORKSPACE_PATH: &str = "/home/sprite";
62
63static SYSTEM_PROMPT: LazyLock<String> = LazyLock::new(|| {
68 let mut prompt = String::from(
69 "Sprites are persistent Firecracker Linux VMs. Create or select a sprite before sprite-scoped operations; data survives idle/sleep, checkpoints can protect risky changes, services should listen on port 8080 for the public URL, and deleting avoids storage charges. Working directory is `/home/sprite`.",
70 );
71 prompt.push_str(everruns_core::tool_output_sanitizer::EXEC_OUTPUT_HINT);
72 prompt
73});
74
75pub struct SpritesCapability;
76
77impl Capability for SpritesCapability {
78 fn id(&self) -> &str {
79 "sprites"
80 }
81
82 fn name(&self) -> &str {
83 "Sprites"
84 }
85
86 fn description(&self) -> &str {
87 "Run code in persistent, hardware-isolated Linux microVMs powered by Sprites. \
88 Create multiple Firecracker VMs per session with full ext4 filesystems, \
89 execute commands, manage files, checkpoint/restore state, and expose HTTP services."
90 }
91
92 fn localizations(&self) -> Vec<CapabilityLocalization> {
93 vec![CapabilityLocalization::text(
94 "uk",
95 "Sprites",
96 "Запускайте код у постійних, апаратно ізольованих мікровіртуальних машинах Linux \
97 на основі Sprites. Створюйте кілька віртуальних машин Firecracker на сесію з \
98 повноцінними файловими системами ext4, виконуйте команди, керуйте файлами, \
99 зберігайте та відновлюйте стан через контрольні точки й публікуйте HTTP-сервіси.",
100 )]
101 }
102
103 fn status(&self) -> CapabilityStatus {
104 CapabilityStatus::Available
105 }
106
107 fn risk_level(&self) -> RiskLevel {
108 RiskLevel::High
109 }
110
111 fn icon(&self) -> Option<&str> {
112 Some("sprites")
113 }
114
115 fn category(&self) -> Option<&str> {
116 Some("Execution")
117 }
118
119 fn system_prompt_addition(&self) -> Option<&str> {
120 Some(&SYSTEM_PROMPT)
121 }
122
123 fn tools(&self) -> Vec<Box<dyn Tool>> {
124 vec![
125 Box::new(SpritesCreateSpriteTool),
126 Box::new(SpritesExecTool),
127 Box::new(SpritesReadFileTool),
128 Box::new(SpritesWriteFileTool),
129 Box::new(SpritesListSpritesTool),
130 Box::new(SpritesManageSpriteTool),
131 Box::new(SpritesCheckpointTool),
132 Box::new(SpritesRestoreCheckpointTool),
133 Box::new(SpritesServiceUrlTool),
134 ]
135 }
136
137 fn dependencies(&self) -> Vec<&'static str> {
138 vec!["session_storage"]
139 }
140
141 fn features(&self) -> Vec<&'static str> {
142 vec![LEASED_RESOURCES_FEATURE]
143 }
144}
145
146#[cfg(test)]
151mod tests {
152 use super::*;
153 use everruns_core::capabilities::CapabilityStatus;
154
155 #[test]
156 fn test_capability_metadata() {
157 let cap = SpritesCapability;
158 assert_eq!(cap.id(), "sprites");
159 assert_eq!(cap.name(), "Sprites");
160 assert_eq!(cap.status(), CapabilityStatus::Available);
161 assert_eq!(cap.icon(), Some("sprites"));
162 assert_eq!(cap.category(), Some("Execution"));
163 }
164
165 #[test]
166 fn test_capability_has_all_tools() {
167 let cap = SpritesCapability;
168 let tools = cap.tools();
169 assert_eq!(tools.len(), 9);
170
171 let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
172 assert!(names.contains(&"sprites_create_sprite"));
173 assert!(names.contains(&"sprites_exec"));
174 assert!(names.contains(&"sprites_read_file"));
175 assert!(names.contains(&"sprites_write_file"));
176 assert!(names.contains(&"sprites_list_sprites"));
177 assert!(names.contains(&"sprites_manage_sprite"));
178 assert!(names.contains(&"sprites_checkpoint"));
179 assert!(names.contains(&"sprites_restore_checkpoint"));
180 assert!(names.contains(&"sprites_service_url"));
181 }
182
183 #[test]
184 fn test_capability_has_system_prompt() {
185 let cap = SpritesCapability;
186 let prompt = cap.system_prompt_addition().unwrap();
187 assert!(prompt.contains("Sprites"));
188 assert!(prompt.contains("persistent Firecracker"));
189 assert!(prompt.contains("checkpoints"));
190 assert!(prompt.contains("port 8080"));
191 assert!(prompt.contains("/home/sprite"));
192 }
193
194 #[tokio::test]
195 async fn system_prompt_within_budget() {
196 let cap = SpritesCapability;
197 let ctx = everruns_core::capabilities::SystemPromptContext::without_file_store(
198 everruns_core::SessionId::new(),
199 );
200 let prompt = cap.system_prompt_contribution(&ctx).await.unwrap();
201 assert!(prompt.len() <= 1400, "prompt is {} bytes", prompt.len());
205 }
206
207 #[test]
208 fn test_all_tools_require_context() {
209 let cap = SpritesCapability;
210 for tool in cap.tools() {
211 assert!(
212 tool.requires_context(),
213 "Tool {} should require context",
214 tool.name()
215 );
216 }
217 }
218
219 #[test]
220 fn test_capability_dependencies() {
221 let cap = SpritesCapability;
222 assert_eq!(cap.dependencies(), vec!["session_storage"]);
223 }
224}