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 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-05 07:52:13 -05:00
parent 059a50e1b3
commit 2d3ae34059
4 changed files with 25 additions and 8 deletions

View file

@ -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");

View file

@ -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<dyn Interviewer>,

View file

@ -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));
}

View file

@ -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<Diagnostic> {
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