use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::workflow_step::UnifiedStep;
fn deserialize_null_as_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
let opt: Option<Vec<T>> = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_default())
}
fn default_expected_status() -> u16 {
200
}
fn default_health_timeout() -> u64 {
30
}
fn default_is_critical() -> bool {
true
}
fn default_retry_delay_ms() -> u64 {
2000
}
fn default_true() -> bool {
true
}
fn default_category() -> String {
"general".to_string()
}
fn default_auto_include_contexts() -> bool {
true
}
fn default_log_watch_enabled() -> bool {
true
}
fn default_health_check_enabled() -> bool {
true
}
fn default_preflight_check_enabled() -> bool {
true
}
fn default_max_sweep_iterations() -> u32 {
5
}
fn default_max_fix_attempts() -> u32 {
3
}
fn default_max_ci_auto_resumes() -> u32 {
10
}
fn default_reflection_mode() -> bool {
true
}
fn default_ai_reviewed() -> bool {
true
}
fn default_multi_agent_mode() -> bool {
true
}
fn is_default_log_source(selection: &LogSourceSelection) -> bool {
matches!(selection, LogSourceSelection::Mode(LogSourceMode::Default))
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct RoutingRule {
#[serde(alias = "condition")]
pub condition: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "model")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "provider")]
pub provider: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "temperature"
)]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "max_tokens")]
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct ModelOverrideConfig {
#[serde(default, skip_serializing_if = "Option::is_none", alias = "provider")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "model")]
pub model: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "temperature"
)]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "max_tokens")]
pub max_tokens: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "fallback_provider"
)]
pub fallback_provider: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "fallback_model"
)]
pub fallback_model: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "routing_rules"
)]
pub routing_rules: Option<Vec<RoutingRule>>,
}
pub type ModelOverrides = HashMap<String, ModelOverrideConfig>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum LogSourceMode {
#[default]
Default,
Ai,
All,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum LogSourceSelection {
Mode(LogSourceMode),
Profile {
profile_id: String,
},
}
impl Default for LogSourceSelection {
fn default() -> Self {
LogSourceSelection::Mode(LogSourceMode::Default)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct HealthCheckUrl {
#[serde(alias = "name")]
pub name: String,
#[serde(alias = "url")]
pub url: String,
#[serde(default = "default_expected_status", alias = "expected_status")]
pub expected_status: u16,
#[serde(default = "default_health_timeout", alias = "timeout_seconds")]
pub timeout_seconds: u64,
#[serde(default = "default_is_critical", alias = "is_critical")]
pub is_critical: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum WorkflowArchitecture {
#[default]
Traditional,
AgenticVerification,
MultiAgentPipeline,
}
impl std::fmt::Display for WorkflowArchitecture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Traditional => write!(f, "traditional"),
Self::AgenticVerification => write!(f, "agentic_verification"),
Self::MultiAgentPipeline => write!(f, "multi_agent_pipeline"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct StageCondition {
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "if_previous"
)]
pub if_previous: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "min_iteration"
)]
pub min_iteration: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "min_failures"
)]
pub min_failures: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct RetryPolicy {
#[serde(default, alias = "count")]
pub count: u32,
#[serde(default = "default_retry_delay_ms", alias = "delay_ms")]
pub delay_ms: u64,
#[serde(default, alias = "backoff")]
pub backoff: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct StageOutput {
#[serde(alias = "key")]
pub key: String,
#[serde(default, alias = "description")]
pub description: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct StageInput {
#[serde(alias = "key")]
pub key: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "from_stage")]
pub from_stage: Option<String>,
#[serde(default = "default_true", alias = "required")]
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct WorkflowStage {
#[serde(default, alias = "id")]
pub id: String,
#[serde(alias = "name")]
pub name: String,
#[serde(default, alias = "description")]
pub description: String,
#[serde(default, alias = "setup_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub setup_steps: Vec<Value>,
#[serde(default, alias = "verification_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub verification_steps: Vec<Value>,
#[serde(default, alias = "agentic_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub agentic_steps: Vec<Value>,
#[serde(default, alias = "completion_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub completion_steps: Vec<Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "max_iterations"
)]
pub max_iterations: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "timeout_seconds"
)]
pub timeout_seconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "provider")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "model")]
pub model: Option<String>,
#[serde(
default,
skip_serializing_if = "HashMap::is_empty",
alias = "model_overrides"
)]
pub model_overrides: ModelOverrides,
#[serde(default, alias = "approval_gate")]
pub approval_gate: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "condition")]
pub condition: Option<StageCondition>,
#[serde(default, alias = "completion_prompts_first")]
pub completion_prompts_first: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "retry_policy"
)]
pub retry_policy: Option<RetryPolicy>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "outputs")]
pub outputs: Option<Vec<StageOutput>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "inputs")]
pub inputs: Option<Vec<StageInput>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(deny_unknown_fields)]
pub struct UnifiedWorkflow {
#[serde(default, alias = "id")]
pub id: String,
#[serde(alias = "name")]
pub name: String,
#[serde(default, alias = "description")]
pub description: String,
#[serde(default = "default_category", alias = "category")]
pub category: String,
#[serde(default, alias = "tags")]
pub tags: Vec<String>,
#[serde(default, alias = "setup_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub setup_steps: Vec<Value>,
#[serde(default, alias = "verification_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub verification_steps: Vec<Value>,
#[serde(default, alias = "agentic_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub agentic_steps: Vec<Value>,
#[serde(default, alias = "completion_steps")]
#[schemars(with = "Vec<UnifiedStep>")]
pub completion_steps: Vec<Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "max_iterations"
)]
pub max_iterations: Option<u32>,
#[serde(default = "default_max_fix_attempts", alias = "max_fix_attempts")]
pub max_fix_attempts: u32,
#[serde(default = "default_max_ci_auto_resumes", alias = "max_ci_auto_resumes")]
pub max_ci_auto_resumes: u32,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "timeout_seconds"
)]
pub timeout_seconds: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "provider")]
pub provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "model")]
pub model: Option<String>,
#[serde(
default,
skip_serializing_if = "HashMap::is_empty",
alias = "model_overrides"
)]
pub model_overrides: ModelOverrides,
#[serde(default, alias = "skip_ai_summary")]
pub skip_ai_summary: bool,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
alias = "targeted_error_ids"
)]
pub targeted_error_ids: Vec<i64>,
#[serde(
default,
skip_serializing_if = "is_default_log_source",
alias = "log_source_selection"
)]
pub log_source_selection: LogSourceSelection,
#[serde(default, skip_serializing_if = "Vec::is_empty", alias = "context_ids")]
pub context_ids: Vec<String>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
alias = "disabled_context_ids"
)]
pub disabled_context_ids: Vec<String>,
#[serde(
default = "default_auto_include_contexts",
alias = "auto_include_contexts"
)]
pub auto_include_contexts: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "prompt_template"
)]
pub prompt_template: Option<String>,
#[serde(default = "default_log_watch_enabled", alias = "log_watch_enabled")]
pub log_watch_enabled: bool,
#[serde(
default = "default_health_check_enabled",
alias = "health_check_enabled"
)]
pub health_check_enabled: bool,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
alias = "health_check_urls"
)]
pub health_check_urls: Vec<HealthCheckUrl>,
#[serde(
default = "default_preflight_check_enabled",
alias = "preflight_check_enabled"
)]
pub preflight_check_enabled: bool,
#[serde(default, alias = "enable_sweep")]
pub enable_sweep: bool,
#[serde(
default = "default_max_sweep_iterations",
alias = "max_sweep_iterations"
)]
pub max_sweep_iterations: u32,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "generated_by_task_run_id"
)]
pub generated_by_task_run_id: Option<String>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
deserialize_with = "deserialize_null_as_empty_vec",
alias = "stages"
)]
pub stages: Vec<WorkflowStage>,
#[serde(default, alias = "stop_on_failure")]
pub stop_on_failure: bool,
#[serde(
default,
skip_serializing_if = "HashMap::is_empty",
alias = "constraint_overrides"
)]
pub constraint_overrides: HashMap<String, bool>,
#[serde(default, alias = "approval_gate")]
pub approval_gate: bool,
#[serde(default = "default_reflection_mode", alias = "reflection_mode")]
pub reflection_mode: bool,
#[serde(default, alias = "completion_prompts_first")]
pub completion_prompts_first: bool,
#[serde(default, alias = "is_favorite")]
pub is_favorite: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "dependency_graph"
)]
pub dependency_graph: Option<Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "cost_annotations"
)]
pub cost_annotations: Option<Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "acceptance_criteria"
)]
pub acceptance_criteria: Option<Value>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "quality_report"
)]
pub quality_report: Option<Value>,
#[serde(default = "default_multi_agent_mode", alias = "multi_agent_mode")]
pub multi_agent_mode: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "rollback_policy"
)]
pub rollback_policy: Option<String>,
#[serde(default, alias = "enforce_token_budget")]
pub enforce_token_budget: bool,
#[serde(default, alias = "strict_cwd")]
pub strict_cwd: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty", alias = "tool_tags")]
pub tool_tags: Vec<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "security_profile"
)]
pub security_profile: Option<String>,
#[serde(default, alias = "use_worktree")]
pub use_worktree: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "auto_commit_subagents"
)]
pub auto_commit_subagents: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "workflow_architecture"
)]
pub workflow_architecture: Option<WorkflowArchitecture>,
#[serde(default = "default_ai_reviewed", alias = "ai_reviewed")]
pub ai_reviewed: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "flow_control_json"
)]
pub flow_control_json: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "phase_timeouts_json"
)]
pub phase_timeouts_json: Option<String>,
#[serde(default, alias = "htn_enabled")]
pub htn_enabled: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "htn_ui_bridge_url"
)]
pub htn_ui_bridge_url: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "htn_state_machine_path"
)]
pub htn_state_machine_path: Option<String>,
#[serde(default, alias = "created_at")]
pub created_at: String,
#[serde(rename = "modified_at", default, alias = "updated_at")]
pub updated_at: String,
}