pub fn benchmark_image(registry: &str, benchmark: &str, tag: &str) -> String {
format!("{registry}/benchmarks/{benchmark}:{tag}")
}
pub fn benchmark_task_image(registry: &str, benchmark: &str, task_id: &str, tag: &str) -> String {
format!(
"{registry}/benchmarks/{benchmark}-{}:{tag}",
task_id.to_lowercase()
)
}
pub fn agent_image(registry: &str, agent: &str, tag: &str) -> String {
format!("{registry}/agents/{agent}:{tag}")
}
pub fn model_image(registry: &str, model: &str, tag: &str) -> String {
format!("{registry}/models/{model}:{tag}")
}
pub fn eval_image(registry: &str, benchmark: &str, agent: &str, tag: &str) -> String {
format!("{registry}/evals/{benchmark}--{agent}:{tag}")
}
pub fn eval_task_image(
registry: &str,
benchmark: &str,
task_id: &str,
agent: &str,
tag: &str,
) -> String {
format!(
"{registry}/evals/{benchmark}-{}--{agent}:{tag}",
task_id.to_lowercase()
)
}
pub fn compose_artifact(registry: &str, benchmark: &str) -> String {
format!("{registry}/compose/{benchmark}:latest")
}
pub fn agent_bake_target(agent: &str) -> String {
format!("agent-{agent}")
}
pub fn benchmark_bake_target(benchmark: &str) -> String {
format!("benchmark-{benchmark}")
}
pub fn model_bake_target(model: &str) -> String {
format!("model-{}", model.replace('.', "_"))
}
pub fn flatten_imagestream(repo: &str) -> String {
let (cat, rest) = repo.split_once('/').unwrap_or(("", repo));
let name = rest.to_lowercase().replace('.', "-").replace("--", "-");
match cat {
"core" | "gateways" => format!("{cat}-{name}"),
_ => name,
}
}
pub fn release_name(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut dash = false;
for c in s.chars() {
let c = c.to_ascii_lowercase();
if c.is_ascii_lowercase() || c.is_ascii_digit() {
out.push(c);
dash = false;
} else if !dash {
out.push('-');
dash = true;
}
}
out.truncate(53);
out.trim_matches('-').to_string()
}
#[cfg(test)]
mod tests {
use super::*;
const REG: &str = "quay.io/eval-containers";
#[test]
fn eval_image_uses_double_dash_separator() {
assert_eq!(
eval_image(REG, "aime", "claude-code", "2.5.0"),
"quay.io/eval-containers/evals/aime--claude-code:2.5.0"
);
}
#[test]
fn category_images_are_namespaced() {
assert_eq!(
benchmark_image(REG, "aime", "latest"),
"quay.io/eval-containers/benchmarks/aime:latest"
);
assert_eq!(
agent_image(REG, "codex", "latest"),
"quay.io/eval-containers/agents/codex:latest"
);
assert_eq!(
model_image(REG, "gpt-5.4--bifrost", "latest"),
"quay.io/eval-containers/models/gpt-5.4--bifrost:latest"
);
}
#[test]
fn per_task_variant_appends_task_id() {
assert_eq!(
benchmark_task_image(REG, "swe-bench", "42", "latest"),
"quay.io/eval-containers/benchmarks/swe-bench-42:latest"
);
}
#[test]
fn per_task_image_refs_lowercase_the_task_id() {
assert_eq!(
benchmark_task_image(
REG,
"swe-bench-pro",
"instance_NodeBB__NodeBB-abc",
"latest"
),
"quay.io/eval-containers/benchmarks/swe-bench-pro-instance_nodebb__nodebb-abc:latest"
);
assert_eq!(
eval_task_image(
REG,
"swe-bench-pro",
"instance_NodeBB__NodeBB-abc",
"claude-code",
"latest"
),
"quay.io/eval-containers/evals/swe-bench-pro-instance_nodebb__nodebb-abc--claude-code:latest"
);
}
#[test]
fn eval_task_image_carries_task_before_agent() {
assert_eq!(
eval_task_image(
REG,
"swe-bench",
"sympy__sympy-24066",
"claude-code",
"latest"
),
"quay.io/eval-containers/evals/swe-bench-sympy__sympy-24066--claude-code:latest"
);
}
#[test]
fn model_bake_target_replaces_dots() {
assert_eq!(model_bake_target("gpt-5.4"), "model-gpt-5_4");
assert_eq!(
model_bake_target("gpt-5.4--bifrost"),
"model-gpt-5_4--bifrost"
);
assert_eq!(model_bake_target("replay"), "model-replay");
}
#[test]
fn flatten_drops_eval_categories_keeps_core() {
assert_eq!(flatten_imagestream("core/otel"), "core-otel");
assert_eq!(flatten_imagestream("gateways/bifrost"), "gateways-bifrost");
assert_eq!(flatten_imagestream("benchmarks/aime"), "aime");
assert_eq!(flatten_imagestream("evals/aime--codex"), "aime-codex");
assert_eq!(flatten_imagestream("models/gpt-5.4"), "gpt-5-4");
}
#[test]
fn release_name_sanitizes_to_a_dns_label() {
assert_eq!(
release_name("swe-bench-claude-code-task-sympy__sympy-24066"),
"swe-bench-claude-code-task-sympy-sympy-24066"
);
assert_eq!(
release_name("aime-claude-code-task-0"),
"aime-claude-code-task-0"
);
}
}