distri_types/configuration/
package.rs

1use crate::ToolDefinition;
2use crate::agent::StandardDefinition;
3use crate::configuration::manifest::DistriServerConfig;
4use crate::configuration::workflow::{
5    CustomAgentDefinition, DagWorkflowDefinition, SequentialWorkflowDefinition,
6};
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10/// Tool definition ready for DAP registration with runtime info
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PluginToolDefinition {
13    pub name: String,
14    pub package_name: String,
15    pub description: String,
16    #[serde(default)]
17    pub parameters: serde_json::Value,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub auth: Option<crate::auth::AuthRequirement>,
20}
21
22/// Workflow definition ready for DAP registration with runtime info
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct PluginWorkflowDefinition {
25    pub name: String,
26    pub package_name: String,
27    pub description: String,
28    #[serde(default)]
29    pub parameters: serde_json::Value,
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub examples: Vec<serde_json::Value>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct AgentConfigWithTools {
36    #[serde(flatten)]
37    pub agent: AgentConfig,
38    #[serde(skip_serializing_if = "Vec::is_empty")]
39    pub resolved_tools: Vec<ToolDefinition>,
40    #[serde(skip_serializing_if = "String::is_empty")]
41    pub markdown: String,
42}
43
44/// Unified agent configuration enum - combines all agent and workflow types
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(tag = "agent_type", rename_all = "snake_case")]
47pub enum AgentConfig {
48    /// Standard markdown-based agent
49    StandardAgent(StandardDefinition),
50    /// Sequential workflow agent with ordered steps
51    SequentialWorkflowAgent(SequentialWorkflowDefinition),
52    /// DAG workflow agent with dependency graph
53    DagWorkflowAgent(DagWorkflowDefinition),
54    /// Custom TypeScript-based agent
55    CustomAgent(CustomAgentDefinition),
56}
57
58impl AgentConfig {
59    /// Get the name of the agent/workflow
60    pub fn get_name(&self) -> &str {
61        match self {
62            AgentConfig::StandardAgent(def) => &def.name,
63            AgentConfig::SequentialWorkflowAgent(def) => &def.name,
64            AgentConfig::DagWorkflowAgent(def) => &def.name,
65            AgentConfig::CustomAgent(def) => &def.name,
66        }
67    }
68
69    /// Get the description of the agent/workflow
70    pub fn get_description(&self) -> &str {
71        match self {
72            AgentConfig::StandardAgent(def) => &def.description,
73            AgentConfig::SequentialWorkflowAgent(def) => &def.description,
74            AgentConfig::DagWorkflowAgent(def) => &def.description,
75            AgentConfig::CustomAgent(def) => &def.description,
76        }
77    }
78
79    /// Validate the configuration
80    pub fn validate(&self) -> anyhow::Result<()> {
81        match self {
82            AgentConfig::StandardAgent(def) => def.validate(),
83            AgentConfig::SequentialWorkflowAgent(def) => {
84                if def.name.is_empty() {
85                    return Err(anyhow::anyhow!("Workflow name cannot be empty"));
86                }
87                if def.steps.is_empty() {
88                    return Err(anyhow::anyhow!("Workflow must have at least one step"));
89                }
90                Ok(())
91            }
92            AgentConfig::DagWorkflowAgent(def) => {
93                if def.name.is_empty() {
94                    return Err(anyhow::anyhow!("Workflow name cannot be empty"));
95                }
96                if def.nodes.is_empty() {
97                    return Err(anyhow::anyhow!("DAG workflow must have at least one node"));
98                }
99                Ok(())
100            }
101            AgentConfig::CustomAgent(def) => {
102                if def.name.is_empty() {
103                    return Err(anyhow::anyhow!("Custom agent name cannot be empty"));
104                }
105                if def.script_path.is_empty() {
106                    return Err(anyhow::anyhow!("Custom agent script_path cannot be empty"));
107                }
108                Ok(())
109            }
110        }
111    }
112
113    /// Get the working directory with fallback chain: agent config -> package config -> DISTRI_HOME -> current_dir
114    pub fn get_working_directory(
115        &self,
116        package_config: Option<&DistriServerConfig>,
117    ) -> anyhow::Result<std::path::PathBuf> {
118        // Fall back to package configuration
119        if let Some(config) = package_config {
120            return config.get_working_directory();
121        }
122
123        // Try DISTRI_HOME environment variable
124        if let Ok(distri_home) = std::env::var("DISTRI_HOME") {
125            return Ok(std::path::PathBuf::from(distri_home));
126        }
127
128        // Fallback to current directory
129        std::env::current_dir()
130            .map_err(|e| anyhow::anyhow!("Failed to get current directory: {}", e))
131    }
132}
133
134/// Agent definition ready for DAP registration with runtime info
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct PluginAgentDefinition {
137    pub name: String,
138    pub package_name: String,
139    pub description: String,
140    pub file_path: PathBuf,
141    /// The full agent configuration (supports all agent types)
142    pub agent_config: AgentConfig,
143}
144
145/// Built DAP package artifact ready for registration in distri
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct PluginArtifact {
148    pub name: String,
149    pub path: PathBuf,
150    pub configuration: crate::configuration::manifest::DistriServerConfig,
151    pub tools: Vec<PluginToolDefinition>,
152    pub workflows: Vec<PluginWorkflowDefinition>,
153    pub agents: Vec<PluginAgentDefinition>,
154}