eval_containers/
naming.rs1pub fn benchmark_image(registry: &str, benchmark: &str, tag: &str) -> String {
13 format!("{registry}/benchmarks/{benchmark}:{tag}")
14}
15
16pub fn benchmark_task_image(registry: &str, benchmark: &str, task_id: &str, tag: &str) -> String {
22 format!(
23 "{registry}/benchmarks/{benchmark}-{}:{tag}",
24 task_id.to_lowercase()
25 )
26}
27
28pub fn agent_image(registry: &str, agent: &str, tag: &str) -> String {
30 format!("{registry}/agents/{agent}:{tag}")
31}
32
33pub fn model_image(registry: &str, model: &str, tag: &str) -> String {
35 format!("{registry}/models/{model}:{tag}")
36}
37
38pub fn eval_image(registry: &str, benchmark: &str, agent: &str, tag: &str) -> String {
43 format!("{registry}/evals/{benchmark}--{agent}:{tag}")
44}
45
46pub fn eval_task_image(
53 registry: &str,
54 benchmark: &str,
55 task_id: &str,
56 agent: &str,
57 tag: &str,
58) -> String {
59 format!(
60 "{registry}/evals/{benchmark}-{}--{agent}:{tag}",
61 task_id.to_lowercase()
62 )
63}
64
65pub fn compose_artifact(registry: &str, benchmark: &str) -> String {
67 format!("{registry}/compose/{benchmark}:latest")
68}
69
70pub fn agent_bake_target(agent: &str) -> String {
72 format!("agent-{agent}")
73}
74
75pub fn benchmark_bake_target(benchmark: &str) -> String {
77 format!("benchmark-{benchmark}")
78}
79
80pub fn model_bake_target(model: &str) -> String {
83 format!("model-{}", model.replace('.', "_"))
84}
85
86pub fn flatten_imagestream(repo: &str) -> String {
91 let (cat, rest) = repo.split_once('/').unwrap_or(("", repo));
92 let name = rest.to_lowercase().replace('.', "-").replace("--", "-");
93 match cat {
94 "core" | "gateways" => format!("{cat}-{name}"),
95 _ => name,
96 }
97}
98
99pub fn release_name(s: &str) -> String {
107 let mut out = String::with_capacity(s.len());
108 let mut dash = false;
109 for c in s.chars() {
110 let c = c.to_ascii_lowercase();
111 if c.is_ascii_lowercase() || c.is_ascii_digit() {
112 out.push(c);
113 dash = false;
114 } else if !dash {
115 out.push('-');
116 dash = true;
117 }
118 }
119 out.truncate(53);
120 out.trim_matches('-').to_string()
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 const REG: &str = "quay.io/eval-containers";
127
128 #[test]
129 fn eval_image_uses_double_dash_separator() {
130 assert_eq!(
131 eval_image(REG, "aime", "claude-code", "2.5.0"),
132 "quay.io/eval-containers/evals/aime--claude-code:2.5.0"
133 );
134 }
135
136 #[test]
137 fn category_images_are_namespaced() {
138 assert_eq!(
139 benchmark_image(REG, "aime", "latest"),
140 "quay.io/eval-containers/benchmarks/aime:latest"
141 );
142 assert_eq!(
143 agent_image(REG, "codex", "latest"),
144 "quay.io/eval-containers/agents/codex:latest"
145 );
146 assert_eq!(
147 model_image(REG, "gpt-5.4--bifrost", "latest"),
148 "quay.io/eval-containers/models/gpt-5.4--bifrost:latest"
149 );
150 }
151
152 #[test]
153 fn per_task_variant_appends_task_id() {
154 assert_eq!(
155 benchmark_task_image(REG, "swe-bench", "42", "latest"),
156 "quay.io/eval-containers/benchmarks/swe-bench-42:latest"
157 );
158 }
159
160 #[test]
161 fn per_task_image_refs_lowercase_the_task_id() {
162 assert_eq!(
164 benchmark_task_image(
165 REG,
166 "swe-bench-pro",
167 "instance_NodeBB__NodeBB-abc",
168 "latest"
169 ),
170 "quay.io/eval-containers/benchmarks/swe-bench-pro-instance_nodebb__nodebb-abc:latest"
171 );
172 assert_eq!(
173 eval_task_image(
174 REG,
175 "swe-bench-pro",
176 "instance_NodeBB__NodeBB-abc",
177 "claude-code",
178 "latest"
179 ),
180 "quay.io/eval-containers/evals/swe-bench-pro-instance_nodebb__nodebb-abc--claude-code:latest"
181 );
182 }
183
184 #[test]
185 fn eval_task_image_carries_task_before_agent() {
186 assert_eq!(
187 eval_task_image(
188 REG,
189 "swe-bench",
190 "sympy__sympy-24066",
191 "claude-code",
192 "latest"
193 ),
194 "quay.io/eval-containers/evals/swe-bench-sympy__sympy-24066--claude-code:latest"
195 );
196 }
197
198 #[test]
199 fn model_bake_target_replaces_dots() {
200 assert_eq!(model_bake_target("gpt-5.4"), "model-gpt-5_4");
201 assert_eq!(
202 model_bake_target("gpt-5.4--bifrost"),
203 "model-gpt-5_4--bifrost"
204 );
205 assert_eq!(model_bake_target("replay"), "model-replay");
206 }
207
208 #[test]
209 fn flatten_drops_eval_categories_keeps_core() {
210 assert_eq!(flatten_imagestream("core/otel"), "core-otel");
211 assert_eq!(flatten_imagestream("gateways/bifrost"), "gateways-bifrost");
212 assert_eq!(flatten_imagestream("benchmarks/aime"), "aime");
213 assert_eq!(flatten_imagestream("evals/aime--codex"), "aime-codex");
214 assert_eq!(flatten_imagestream("models/gpt-5.4"), "gpt-5-4");
215 }
216
217 #[test]
218 fn release_name_sanitizes_to_a_dns_label() {
219 assert_eq!(
221 release_name("swe-bench-claude-code-task-sympy__sympy-24066"),
222 "swe-bench-claude-code-task-sympy-sympy-24066"
223 );
224 assert_eq!(
226 release_name("aime-claude-code-task-0"),
227 "aime-claude-code-task-0"
228 );
229 }
230}