From 2d3ae34059f186238f9b66a84f986e00bead3f4c Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 5 Mar 2026 07:52:13 -0500 Subject: [PATCH] Extract is_llm_handler_type() to deduplicate 4 match-arm sites The pattern Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot") was duplicated across preamble.rs (3x) and validation/rules.rs (1x). Centralizes into a single function in graph/types.rs. Also fixes stale doc comment on default_registry. Co-Authored-By: Claude Opus 4.6 --- crates/arc-workflows/src/graph/types.rs | 17 +++++++++++++++++ crates/arc-workflows/src/handler/mod.rs | 4 ++-- crates/arc-workflows/src/preamble.rs | 8 ++++---- crates/arc-workflows/src/validation/rules.rs | 4 ++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/crates/arc-workflows/src/graph/types.rs b/crates/arc-workflows/src/graph/types.rs index a22417b84..b1939c478 100644 --- a/crates/arc-workflows/src/graph/types.rs +++ b/crates/arc-workflows/src/graph/types.rs @@ -67,6 +67,12 @@ impl AttrValue { } } +/// Returns true if the handler type is an LLM-based handler (agent or prompt, including legacy aliases). +#[must_use] +pub fn is_llm_handler_type(handler_type: Option<&str>) -> bool { + matches!(handler_type, Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot")) +} + /// Maps Graphviz shapes to handler type strings (Section 2.8). #[must_use] pub fn shape_to_handler_type(shape: &str) -> Option<&'static str> { @@ -480,6 +486,17 @@ mod tests { assert_eq!(shape_to_handler_type("unknown"), None); } + #[test] + fn is_llm_handler_type_checks() { + assert!(is_llm_handler_type(Some("agent"))); + assert!(is_llm_handler_type(Some("agent_loop"))); + assert!(is_llm_handler_type(Some("prompt"))); + assert!(is_llm_handler_type(Some("one_shot"))); + assert!(!is_llm_handler_type(Some("command"))); + assert!(!is_llm_handler_type(Some("human"))); + assert!(!is_llm_handler_type(None)); + } + #[test] fn node_defaults() { let node = Node::new("test"); diff --git a/crates/arc-workflows/src/handler/mod.rs b/crates/arc-workflows/src/handler/mod.rs index 398d16bb7..28d4eb098 100644 --- a/crates/arc-workflows/src/handler/mod.rs +++ b/crates/arc-workflows/src/handler/mod.rs @@ -114,8 +114,8 @@ impl HandlerRegistry { /// Build a [`HandlerRegistry`] with all built-in handler types registered. /// -/// The `make_backend` closure is called once per handler that needs a backend -/// (`AgentHandler` default, explicit `"agent"`, and `"parallel.fan_in"`). +/// The `make_backend` closure is called for each handler that needs a backend +/// (default, `"agent"`, `"agent_loop"`, `"prompt"`, `"one_shot"`, and `"parallel.fan_in"`). #[must_use] pub fn default_registry( interviewer: Arc, diff --git a/crates/arc-workflows/src/preamble.rs b/crates/arc-workflows/src/preamble.rs index 9a6214d30..1eb62798c 100644 --- a/crates/arc-workflows/src/preamble.rs +++ b/crates/arc-workflows/src/preamble.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use crate::artifact::{artifact_path, format_artifact_reference}; use crate::context::Context; -use crate::graph::{Graph, Node}; +use crate::graph::{is_llm_handler_type, Graph, Node}; use crate::outcome::Outcome; /// Build a fidelity-appropriate preamble string for non-full context modes. @@ -157,7 +157,7 @@ fn render_compact_stage_details( } lines } - Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot") => { + h if is_llm_handler_type(h) => { let mut lines = Vec::new(); if let Some(usage) = &outcome.usage { let input = format_token_count(usage.input_tokens); @@ -234,7 +234,7 @@ fn render_summary_high_stage_section( } } } - Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot") => { + h if is_llm_handler_type(h) => { if let Some(usage) = &outcome.usage { lines.push(format!("- Model: {}", usage.model)); lines.push(format!( @@ -497,7 +497,7 @@ fn build_summary_preamble( } } } - Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot") => { + h if is_llm_handler_type(h) => { if let Some(usage) = &outcome.usage { parts.push(format!(" - Model: {}", usage.model)); } diff --git a/crates/arc-workflows/src/validation/rules.rs b/crates/arc-workflows/src/validation/rules.rs index b947c1b05..c79a33d45 100644 --- a/crates/arc-workflows/src/validation/rules.rs +++ b/crates/arc-workflows/src/validation/rules.rs @@ -2,7 +2,7 @@ use std::collections::{HashSet, VecDeque}; use std::str::FromStr; use crate::condition::parse_condition; -use crate::graph::{AttrValue, Graph}; +use crate::graph::{is_llm_handler_type, AttrValue, Graph}; use super::{Diagnostic, LintRule, Severity}; @@ -620,7 +620,7 @@ impl LintRule for PromptOnLlmNodesRule { fn apply(&self, graph: &Graph) -> Vec { let mut diagnostics = Vec::new(); for node in graph.nodes.values() { - if matches!(node.handler_type(), Some("agent") | Some("agent_loop") | Some("prompt") | Some("one_shot")) { + if is_llm_handler_type(node.handler_type()) { let has_prompt = node.prompt().is_some_and(|p| !p.is_empty()); let has_label = node .attrs