use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, LitStr, Expr, Token, braced, parse::{Parse, ParseStream}};
use syn::punctuated::Punctuated;
struct WorkflowStep {
name: LitStr,
agent: Expr,
instructions: Option<LitStr>,
tools: Option<Expr>,
when: Option<Expr>,
}
impl Parse for WorkflowStep {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let _ = braced!(content in input);
let mut name = None;
let mut agent = None;
let mut instructions = None;
let mut tools = None;
let mut when = None;
while !content.is_empty() {
let key: syn::Ident = content.parse()?;
let _: Token![:] = content.parse()?;
match key.to_string().as_str() {
"name" => {
name = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"agent" => {
agent = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"instructions" => {
instructions = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"tools" => {
tools = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"when" => {
when = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
_ => return Err(syn::Error::new(key.span(), "Unknown field in workflow step")),
}
}
let name = name.ok_or_else(|| syn::Error::new(content.span(), "Missing 'name' field in workflow step"))?;
let agent = agent.ok_or_else(|| syn::Error::new(content.span(), "Missing 'agent' field in workflow step"))?;
Ok(WorkflowStep {
name,
agent,
instructions,
tools,
when,
})
}
}
struct WorkflowDef {
name: LitStr,
description: Option<LitStr>,
steps: Punctuated<WorkflowStep, Token![,]>,
}
impl Parse for WorkflowDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let _ = braced!(content in input);
let mut name = None;
let mut description = None;
let mut steps_content = None;
while !content.is_empty() {
let key: syn::Ident = content.parse()?;
let _: Token![:] = content.parse()?;
match key.to_string().as_str() {
"name" => {
name = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"description" => {
description = Some(content.parse()?);
let _: Option<Token![,]> = content.parse()?;
},
"steps" => {
let steps_braced;
braced!(steps_braced in content);
steps_content = Some(steps_braced);
let _: Option<Token![,]> = content.parse()?;
},
_ => return Err(syn::Error::new(key.span(), "Unknown field in workflow definition")),
}
}
let name = name.ok_or_else(|| syn::Error::new(content.span(), "Missing 'name' field in workflow definition"))?;
let steps_content = steps_content.ok_or_else(||
syn::Error::new(content.span(), "Missing 'steps' field in workflow definition")
)?;
let steps = Punctuated::parse_terminated(&steps_content)?;
Ok(WorkflowDef {
name,
description,
steps,
})
}
}
pub fn workflow_impl(input: TokenStream) -> TokenStream {
let workflow_def = parse_macro_input!(input as WorkflowDef);
let workflow_name = &workflow_def.name;
let workflow_name_str = workflow_name.value();
let workflow_var_name = format_ident!("{}", workflow_name_str.to_lowercase());
let description = match &workflow_def.description {
Some(desc) => quote! { .with_description(#desc) },
None => quote! {},
};
let mut step_defs = Vec::new();
let mut step_registrations = Vec::new();
for step in workflow_def.steps.iter() {
let step_name = &step.name;
let step_name_str = step_name.value();
let step_var_name = format_ident!("step_{}", step_name_str.to_lowercase());
let agent = &step.agent;
let instructions = match &step.instructions {
Some(instr) => quote! { .with_instructions(#instr) },
None => quote! {},
};
let tools = match &step.tools {
Some(tools_expr) => quote! { .with_tools(#tools_expr) },
None => quote! {},
};
let step_def = quote! {
let #step_var_name = lumosai_core::workflow::WorkflowStep::new(#step_name)
.with_agent(#agent)
#instructions
#tools;
};
step_defs.push(step_def);
let step_reg = if let Some(when) = &step.when {
quote! {
workflow_def.add_step_with_condition(#step_var_name, |ctx| {
#when
});
}
} else {
quote! {
workflow_def.add_step(#step_var_name);
}
};
step_registrations.push(step_reg);
}
let expanded = quote! {
{
#(#step_defs)*
let mut workflow_def = lumosai_core::workflow::WorkflowDefinition::new(None)
.with_name(#workflow_name)
#description;
#(#step_registrations)*
let #workflow_var_name = workflow_def;
#workflow_var_name
}
};
TokenStream::from(expanded)
}