distri_types/configuration/
manifest.rs1use anyhow::{Result, anyhow};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5use tokio::fs;
6
7use crate::agent::ModelSettings;
9use crate::configuration::config::{ExternalMcpServer, ServerConfig, StoreConfig};
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13pub struct DistriServerConfig {
14 pub name: String,
15 pub version: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub description: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub license: Option<String>,
20
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub agents: Option<Vec<String>>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub entrypoints: Option<EntryPoints>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub distri: Option<EngineConfig>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub authors: Option<AuthorsConfig>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub registry: Option<RegistryConfig>,
34
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub mcp_servers: Option<Vec<ExternalMcpServer>>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub server: Option<ServerConfig>,
40
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub model_settings: Option<ModelSettings>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub analysis_model_settings: Option<ModelSettings>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub keywords: Option<Vec<String>>,
48
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub stores: Option<StoreConfig>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub filesystem: Option<crate::configuration::config::ObjectStorageConfig>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BuildConfig {
59 pub command: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub working_dir: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub env: Option<std::collections::HashMap<String, String>>,
67}
68
69impl DistriServerConfig {
70 pub fn has_entrypoints(&self) -> bool {
71 self.entrypoints.is_some()
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(tag = "type")]
77#[serde(rename_all = "lowercase")]
78pub struct EntryPoints {
79 pub path: String,
80}
81
82impl EntryPoints {
83 pub fn validate(&self) -> Result<()> {
85 if self.path.is_empty() {
86 return Err(anyhow!("TypeScript entrypoint path cannot be empty"));
87 }
88 Ok(())
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct EngineConfig {
95 pub engine: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct AuthorsConfig {
100 pub primary: String,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct RegistryConfig {
105 pub url: Option<String>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct LockFile {
110 pub packages: HashMap<String, String>,
111 pub sources: HashMap<String, String>,
112}
113
114impl DistriServerConfig {
115 pub fn get_working_directory(&self) -> Result<std::path::PathBuf> {
117 std::env::current_dir().map_err(|e| anyhow!("Failed to get current directory: {}", e))
119 }
120
121 pub async fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
122 let content = fs::read_to_string(path).await?;
123 let manifest: DistriServerConfig = toml::from_str(&content)?;
124 manifest.validate()?;
125 Ok(manifest)
126 }
127
128 pub fn validate(&self) -> Result<()> {
130 let has_agents = self.agents.as_ref().map_or(false, |a| !a.is_empty());
131 let has_entrypoints = self.entrypoints.is_some();
132
133 if !has_agents && !has_entrypoints {
134 return Err(anyhow!(
135 "Package '{}' must define either agents or entrypoints (for tools/workflows)",
136 self.name
137 ));
138 }
139
140 if let Some(entrypoints) = &self.entrypoints {
142 entrypoints.validate()?;
143 }
144
145 Ok(())
146 }
147
148 pub async fn save_to_path<P: AsRef<Path>>(&self, path: P) -> Result<()> {
149 let content = toml::to_string_pretty(self)?;
150 fs::write(path, content).await?;
151 Ok(())
152 }
153
154 pub fn new_minimal(package_name: String) -> Self {
155 Self {
156 name: package_name,
157 version: "0.1.0".to_string(),
158 description: None,
159 license: Some("Apache-2.0".to_string()),
160 agents: Some(vec![]),
161 entrypoints: None, distri: Some(EngineConfig {
163 engine: ">=0.1.2".to_string(),
164 }),
165 authors: None,
166 registry: None,
167 mcp_servers: None,
168 server: None,
169 stores: None,
170 model_settings: None,
171 analysis_model_settings: None,
172 keywords: None,
173 filesystem: None,
174 }
175 }
176 pub fn current_dir() -> Result<std::path::PathBuf> {
177 let current_dir = std::env::current_dir()?;
178 Ok(current_dir)
179 }
180
181 pub fn find_configuration_in_current_dir() -> Result<std::path::PathBuf> {
182 let current_dir = Self::current_dir()?;
183 println!("current_dir: {:?}", current_dir);
184 let manifest_path = current_dir.join("distri.toml");
185
186 if manifest_path.exists() {
187 Ok(manifest_path)
188 } else {
189 Err(anyhow!("No distri.toml found in current directory"))
190 }
191 }
192}
193
194impl LockFile {
195 pub async fn load_from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
196 let content = fs::read_to_string(path).await?;
197 let lock_file: LockFile = toml::from_str(&content)?;
198 Ok(lock_file)
199 }
200
201 pub async fn save_to_path<P: AsRef<Path>>(&self, path: P) -> Result<()> {
202 let content = toml::to_string_pretty(self)?;
203 fs::write(path, content).await?;
204 Ok(())
205 }
206
207 pub fn new() -> Self {
208 Self {
209 packages: HashMap::new(),
210 sources: HashMap::new(),
211 }
212 }
213}
214
215impl Default for LockFile {
216 fn default() -> Self {
217 Self::new()
218 }
219}