Skip to main content

ares/api/handlers/
workflows.rs

1//! Workflow execution handler
2//!
3//! Handles HTTP requests for executing declarative workflows defined in ares.toml.
4
5use crate::{
6    auth::middleware::AuthUser,
7    types::{AgentContext, Result, WorkflowRequest},
8    workflows::{WorkflowEngine, WorkflowOutput},
9    AppState,
10};
11use axum::{
12    extract::{Path, State},
13    Json,
14};
15use uuid::Uuid;
16
17/// Execute a workflow by name
18///
19/// This endpoint executes a workflow defined in ares.toml. The workflow determines
20/// which agents are used and how they interact to process the request.
21#[utoipa::path(
22    post,
23    path = "/api/workflows/{workflow_name}",
24    request_body = WorkflowRequest,
25    responses(
26        (status = 200, description = "Workflow executed successfully", body = WorkflowOutput),
27        (status = 400, description = "Invalid input"),
28        (status = 401, description = "Unauthorized"),
29        (status = 404, description = "Workflow not found")
30    ),
31    params(
32        ("workflow_name" = String, Path, description = "Name of the workflow to execute")
33    ),
34    tag = "workflows",
35    security(("bearer" = []))
36)]
37pub async fn execute_workflow(
38    State(state): State<AppState>,
39    AuthUser(claims): AuthUser,
40    Path(workflow_name): Path<String>,
41    Json(payload): Json<WorkflowRequest>,
42) -> Result<Json<WorkflowOutput>> {
43    // Create workflow engine
44    let workflow_engine = WorkflowEngine::new(state.clone());
45
46    // Check if workflow exists
47    if !workflow_engine.has_workflow(&workflow_name) {
48        return Err(crate::types::AppError::NotFound(format!(
49            "Workflow '{}' not found",
50            workflow_name
51        )));
52    }
53
54    // Create agent context
55    let context = AgentContext {
56        user_id: claims.sub.clone(),
57        session_id: Uuid::new_v4().to_string(),
58        conversation_history: vec![],
59        user_memory: None,
60    };
61
62    // Execute the workflow
63    let output = workflow_engine
64        .execute_workflow(&workflow_name, &payload.query, &context)
65        .await?;
66
67    Ok(Json(output))
68}
69
70/// List available workflows
71///
72/// Returns a list of workflow names that are defined in the configuration.
73#[utoipa::path(
74    get,
75    path = "/api/workflows",
76    responses(
77        (status = 200, description = "List of available workflows"),
78        (status = 401, description = "Unauthorized")
79    ),
80    tag = "workflows",
81    security(("bearer" = []))
82)]
83pub async fn list_workflows(
84    State(state): State<AppState>,
85    AuthUser(_claims): AuthUser,
86) -> Result<Json<Vec<WorkflowInfo>>> {
87    let config = state.config_manager.config();
88
89    let workflows: Vec<WorkflowInfo> = config
90        .workflows
91        .iter()
92        .map(|(name, wf)| WorkflowInfo {
93            name: name.clone(),
94            entry_agent: wf.entry_agent.clone(),
95            fallback_agent: wf.fallback_agent.clone(),
96            max_depth: wf.max_depth,
97            max_iterations: wf.max_iterations,
98            parallel_subagents: wf.parallel_subagents,
99        })
100        .collect();
101
102    Ok(Json(workflows))
103}
104
105/// Information about a workflow
106#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
107pub struct WorkflowInfo {
108    /// Workflow name
109    pub name: String,
110    /// Agent that starts the workflow
111    pub entry_agent: String,
112    /// Fallback agent if entry fails
113    pub fallback_agent: Option<String>,
114    /// Maximum agent delegation depth
115    pub max_depth: u8,
116    /// Maximum workflow iterations
117    pub max_iterations: u8,
118    /// Whether subagents run in parallel
119    pub parallel_subagents: bool,
120}