use indexmap::IndexMap;
use super::ids::{TaskId, TaskTable};
use super::task::AnalyzedTask;
use crate::ast::agent_def::AgentDef;
use crate::ast::artifact::ArtifactsConfig;
use crate::ast::logging::LogConfig;
use crate::source::Span;
#[derive(Debug, Clone)]
pub struct AnalyzedWorkflow {
pub schema_version: SchemaVersion,
pub name: Option<String>,
pub description: Option<String>,
pub provider: Option<String>,
pub model: Option<String>,
pub task_table: TaskTable,
pub tasks: Vec<AnalyzedTask>,
pub mcp_servers: IndexMap<String, AnalyzedMcpServer>,
pub context_files: Vec<AnalyzedContextFile>,
pub imports: Vec<AnalyzedImportSpec>,
pub inputs: IndexMap<String, serde_json::Value>,
pub artifacts: Option<ArtifactsConfig>,
pub log: Option<LogConfig>,
pub agents: Option<IndexMap<String, AgentDef>>,
pub span: Span,
}
impl Default for AnalyzedWorkflow {
fn default() -> Self {
Self {
schema_version: SchemaVersion::V12,
name: None,
description: None,
provider: None,
model: None,
task_table: TaskTable::new(),
tasks: Vec::new(),
mcp_servers: IndexMap::new(),
context_files: Vec::new(),
imports: Vec::new(),
inputs: IndexMap::new(),
artifacts: None,
log: None,
agents: None,
span: Span::dummy(),
}
}
}
impl AnalyzedWorkflow {
pub fn get_task(&self, id: TaskId) -> Option<&AnalyzedTask> {
self.tasks.iter().find(|t| t.id == id)
}
pub fn get_task_by_name(&self, name: &str) -> Option<&AnalyzedTask> {
let id = self.task_table.get_id(name)?;
self.get_task(id)
}
pub fn task_count(&self) -> usize {
self.tasks.len()
}
pub fn iter_tasks(&self) -> impl Iterator<Item = &AnalyzedTask> {
self.tasks.iter()
}
pub fn has_task(&self, name: &str) -> bool {
self.task_table.get_id(name).is_some()
}
pub fn compute_hash(&self) -> String {
use xxhash_rust::xxh3::xxh3_64;
let mut input = String::new();
input.push_str(self.schema_version.as_str());
if let Some(ref provider) = self.provider {
input.push_str(provider);
}
if let Some(ref model) = self.model {
input.push_str(model);
}
input.push_str(&self.tasks.len().to_string());
for task in &self.tasks {
input.push_str(&task.name);
}
let hash = xxh3_64(input.as_bytes());
format!("{:016x}", hash)
}
}
pub use super::super::schema::SchemaVersion;
#[derive(Debug, Clone)]
pub struct AnalyzedMcpServer {
pub name: String,
pub command: Option<String>,
pub args: Vec<String>,
pub env: IndexMap<String, String>,
pub cwd: Option<String>,
pub url: Option<String>,
pub transport: McpTransport,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum McpTransport {
#[default]
Stdio,
Sse,
}
#[derive(Debug, Clone)]
pub struct AnalyzedContextFile {
pub path: String,
pub alias: Option<String>,
pub max_bytes: Option<u64>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct AnalyzedImportSpec {
pub path: String,
pub prefix: Option<String>,
pub span: Span,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyzed_workflow_default() {
let workflow = AnalyzedWorkflow::default();
assert_eq!(workflow.task_count(), 0);
assert!(workflow.name.is_none());
assert!(workflow.span.is_dummy());
}
#[test]
fn test_analyzed_workflow_task_lookup() {
use crate::binding::WithSpec;
let mut workflow = AnalyzedWorkflow::default();
let id1 = workflow.task_table.insert("task1");
let id2 = workflow.task_table.insert("task2");
workflow.tasks.push(AnalyzedTask {
id: id1,
name: "task1".to_string(),
description: None,
action: super::super::task::AnalyzedTaskAction::default(),
provider: None,
model: None,
with_spec: WithSpec::default(),
depends_on: Vec::new(),
implicit_deps: Vec::new(),
output: None,
for_each: None,
retry: None,
decompose: None,
concurrency: None,
fail_fast: None,
artifact: None,
log: None,
structured: None,
span: Span::dummy(),
});
workflow.tasks.push(AnalyzedTask {
id: id2,
name: "task2".to_string(),
description: None,
action: super::super::task::AnalyzedTaskAction::default(),
provider: None,
model: None,
with_spec: WithSpec::default(),
depends_on: Vec::new(),
implicit_deps: Vec::new(),
output: None,
for_each: None,
retry: None,
decompose: None,
concurrency: None,
fail_fast: None,
artifact: None,
log: None,
structured: None,
span: Span::dummy(),
});
assert_eq!(workflow.task_count(), 2);
assert!(workflow.has_task("task1"));
assert!(workflow.has_task("task2"));
assert!(!workflow.has_task("unknown"));
assert!(workflow.get_task(id1).is_some());
assert_eq!(workflow.get_task(id1).unwrap().name, "task1");
assert!(workflow.get_task_by_name("task2").is_some());
}
}