1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Stdio};
6use std::sync::Mutex;
7use std::time::Duration;
8
9use superfluid_engine::artifact::{Device, Format, TokenizerSource};
10use serde_json::Value;
11
12use crate::runtime_pick::RuntimeId;
13
14const CHECK_TIMEOUT: Duration = Duration::from_secs(60);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Source {
18 Env,
19 BesideDaemon,
20 BuiltIn,
21 Installed,
22 Shipped,
23}
24
25impl Source {
26 pub fn name(self) -> &'static str {
27 match self {
28 Source::Env => "environment",
29 Source::BesideDaemon => "beside superfluid",
30 Source::BuiltIn => "built in",
31 Source::Installed => "installed",
32 Source::Shipped => "shipped with superfluid",
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Worker {
39 pub bin: PathBuf,
40 pub prefix: Vec<String>,
41 pub source: Source,
42}
43
44impl Worker {
45 pub fn install(&self) -> Option<String> {
46 if self.source != Source::Installed {
47 return None;
48 }
49 self.bin.parent()?.parent()?.file_name().and_then(|n| n.to_str()).map(str::to_string)
50 }
51}
52
53#[derive(Debug, Clone, Default, PartialEq)]
54pub struct Info {
55 pub aliases: Vec<String>,
56 pub formats: Vec<Format>,
57 pub tokenizer: Option<TokenizerSource>,
58 pub named: Option<String>,
59}
60
61impl Info {
62 fn from_report(report: &Value) -> Info {
63 use superfluid_worker::check::{formats_from, tokenizer_from};
64 Info {
65 aliases: report["runtime"]["aliases"]
66 .as_array()
67 .map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect())
68 .unwrap_or_default(),
69 formats: formats_from(&report["formats"]),
70 tokenizer: tokenizer_from(&report["tokenizer"]),
71 named: report["named"].as_str().map(str::to_string),
72 }
73 }
74
75 pub fn format_of(&self, path: &Path) -> Option<&Format> {
76 self.formats.iter().find(|f| f.matches(path))
77 }
78
79 pub fn reads_described(&self) -> String {
80 let all: Vec<&str> = self.formats.iter().map(|f| f.describe.as_str()).collect();
81 match all.as_slice() {
82 [] => "no format: its worker declares none, so it is older than this superfluid (rebuild or update it)".to_string(),
83 [one] => one.to_string(),
84 [init @ .., last] => format!("{} or {last}", init.join(", ")),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
90pub struct Runtime {
91 pub id: RuntimeId,
92 pub worker: Worker,
93 pub version: Option<String>,
94 pub capabilities: Option<String>,
95 pub info: Info,
96 pub devices: Vec<Device>,
97 pub tokenizer: Option<PathBuf>,
98 pub pull: Option<Vec<(String, bool)>>,
99}
100
101impl Runtime {
102 pub fn serve_args(&self, model: &Path, ctx: i32, max_batch: u32, kv_bits: i32) -> Vec<String> {
103 let mut args = self.worker.prefix.clone();
104 args.extend([
105 "--model".to_string(),
106 model.to_string_lossy().into_owned(),
107 "--max-context".to_string(),
108 ctx.to_string(),
109 "--max-batch".to_string(),
110 max_batch.to_string(),
111 ]);
112 if !matches!(self.static_capabilities().get("load", "kv_bits"), crate::capabilities::Cap::No(_)) {
113 args.push("--kv-bits".to_string());
114 args.push(kv_bits.to_string());
115 }
116 args
117 }
118
119 pub fn static_capabilities(&self) -> crate::capabilities::Capabilities {
120 self.capabilities.as_deref().map(crate::capabilities::Capabilities::parse).unwrap_or_default()
121 }
122
123 pub fn in_process_capable(&self) -> bool {
124 crate::linked::get(self.id).is_some()
125 }
126
127 pub fn device(&self) -> Option<&Device> {
128 self.devices.first()
129 }
130}
131
132#[derive(Debug, Clone)]
133pub struct Found {
134 pub id: RuntimeId,
135 pub worker: Option<Worker>,
136 pub info: Option<Info>,
137 pub usable: Result<Runtime, String>,
138}
139
140pub struct Catalog {
141 home: PathBuf,
142 beside: Option<PathBuf>,
143 workerd: PathBuf,
144 usable: Mutex<HashMap<RuntimeId, Runtime>>,
145}
146
147pub fn default_home() -> PathBuf {
148 home_from(std::env::var_os("SUPERFLUID_HOME"), std::env::home_dir())
149}
150
151fn home_from(superfluid_home: Option<std::ffi::OsString>, user_home: Option<PathBuf>) -> PathBuf {
152 if let Some(h) = superfluid_home.filter(|h| !h.is_empty()) {
153 return PathBuf::from(h);
154 }
155 user_home.filter(|h| h.is_absolute()).unwrap_or_else(|| PathBuf::from("/")).join(".superfluid")
156}
157
158pub fn worker_name(id: RuntimeId) -> String {
159 format!("superfluid-worker-{}", id.base().name())
160}
161
162pub fn worker_env(id: RuntimeId) -> String {
163 format!("SUPERFLUID_WORKER_{}", id.base().name().to_ascii_uppercase().replace(['-', '.'], "_"))
164}
165
166impl Catalog {
167 pub fn new(home: PathBuf, beside: Option<PathBuf>, workerd: PathBuf) -> Catalog {
168 Catalog { home, beside, workerd, usable: Mutex::new(HashMap::new()) }
169 }
170
171 pub fn from_env() -> Catalog {
172 let beside = std::env::current_exe().ok().and_then(|p| p.parent().map(Path::to_path_buf));
173 let workerd = beside.clone().unwrap_or_default().join("superfluid-workerd");
174 Catalog::new(default_home(), beside, workerd)
175 }
176
177 pub fn with_workerd(workerd: PathBuf) -> Catalog {
180 let beside = std::env::current_exe().ok().and_then(|p| p.parent().map(Path::to_path_buf));
181 Catalog::new(default_home(), beside, workerd)
182 }
183
184 pub fn home(&self) -> &Path {
185 &self.home
186 }
187
188 pub fn workerd(&self) -> &Path {
190 &self.workerd
191 }
192
193 pub fn beside(&self) -> Option<&Path> {
194 self.beside.as_deref()
195 }
196
197 pub fn package_dir(&self, id: RuntimeId) -> PathBuf {
198 self.home.join("runtimes").join(id.base().name())
199 }
200
201 fn shipped_dirs(&self) -> Vec<PathBuf> {
202 let Some(beside) = &self.beside else { return Vec::new() };
203 [beside.join("..").join("libexec").join("superfluid"), beside.join("libexec").join("superfluid")]
204 .into_iter()
205 .filter_map(|d| std::fs::canonicalize(d).ok())
206 .filter(|d| d.is_dir())
207 .collect()
208 }
209
210 pub fn shipped(&self, id: RuntimeId) -> Option<PathBuf> {
211 let own = worker_name(id);
212 self.shipped_dirs().into_iter().map(|d| d.join(&own)).find(|p| is_executable(p))
213 }
214
215 pub fn shipped_ids(&self) -> Vec<RuntimeId> {
216 let mut ids: Vec<RuntimeId> = self.shipped_dirs().iter().flat_map(|d| workers_in(d)).collect();
217 ids.sort();
218 ids.dedup();
219 ids
220 }
221
222 pub fn ids(&self) -> Vec<RuntimeId> {
223 let mut ids: Vec<RuntimeId> = Vec::new();
224 for (k, _) in std::env::vars_os() {
225 if let Some(id) = k.to_str().and_then(|k| k.strip_prefix("SUPERFLUID_WORKER_")) {
226 ids.extend(RuntimeId::parse(&id.to_ascii_lowercase()));
227 }
228 }
229 if let Some(dir) = &self.beside {
230 ids.extend(workers_in(dir));
231 }
232 ids.extend(self.shipped_ids());
233 ids.extend(crate::linked::ids());
234 if let Ok(rd) = std::fs::read_dir(self.home.join("runtimes")) {
235 ids.extend(
236 rd.flatten()
237 .filter(|e| e.path().is_dir())
238 .filter_map(|e| e.file_name().to_str().and_then(RuntimeId::parse)),
239 );
240 }
241 ids.sort();
242 ids.dedup();
243 ids
244 }
245
246 pub fn locate(&self, id: RuntimeId) -> Option<Worker> {
247 if let Some(install) = id.install() {
248 let bin = self.package_dir(id).join(install).join("bin").join(worker_name(id));
249 return std::fs::canonicalize(bin).ok().filter(|p| p.is_file()).map(|bin| Worker {
250 bin,
251 prefix: Vec::new(),
252 source: Source::Installed,
253 });
254 }
255 if let Some(p) = std::env::var_os(worker_env(id)) {
256 return Some(Worker { bin: PathBuf::from(p), prefix: Vec::new(), source: Source::Env });
257 }
258 if crate::linked::get(id).is_some() {
259 return Some(Worker {
260 bin: self.workerd.clone(),
261 prefix: vec!["--engine".into(), id.name().into()],
262 source: Source::BuiltIn,
263 });
264 }
265 let own = worker_name(id);
266 if let Some(bin) = self.beside.as_ref().map(|d| d.join(&own)).filter(|p| is_executable(p)) {
267 return Some(Worker { bin, prefix: Vec::new(), source: Source::BesideDaemon });
268 }
269 let bin = self.package_dir(id).join("current").join("bin").join(&own);
270 let installed = std::fs::canonicalize(bin).ok().filter(|p| p.is_file()).map(|bin| Worker {
271 bin,
272 prefix: Vec::new(),
273 source: Source::Installed,
274 });
275 installed.or_else(|| self.shipped(id).map(|bin| Worker { bin, prefix: Vec::new(), source: Source::Shipped }))
276 }
277
278 pub fn require(&self, id: RuntimeId) -> Result<Runtime, String> {
279 if let Some(r) = self.remembered(id) {
280 return Ok(r);
281 }
282 let found = self.inspect(id);
283 if let Ok(runtime) = &found.usable {
284 self.usable.lock().expect("runtime catalog").insert(id, runtime.clone());
285 }
286 found.usable
287 }
288
289 fn remembered(&self, id: RuntimeId) -> Option<Runtime> {
290 let mut usable = self.usable.lock().expect("runtime catalog");
291 let r = usable.get(&id)?;
292 if r.worker.source != Source::BuiltIn && !r.worker.bin.is_file() {
293 usable.remove(&id);
294 return None;
295 }
296 Some(r.clone())
297 }
298
299 pub fn inspect(&self, id: RuntimeId) -> Found {
300 if let Some(r) = self.remembered(id) {
301 return Found { id, worker: Some(r.worker.clone()), info: Some(r.info.clone()), usable: Ok(r) };
302 }
303 let Some(worker) = self.locate(id) else {
304 return Found { id, worker: None, info: None, usable: Err(self.missing(id)) };
305 };
306 let report = if worker.source == Source::BuiltIn && !worker.bin.is_file() {
307 crate::linked::report(id, None).ok_or_else(|| format!("this build does not link the {id} runtime"))
308 } else {
309 run_check(&worker)
310 };
311 let report = match report {
312 Ok(r) => r,
313 Err(why) => return Found { id, worker: Some(worker), info: None, usable: Err(why) },
314 };
315 let info = Info::from_report(&report);
316 let usable = runtime_from_check(id, worker.clone(), &report).and_then(|r| self.with_tokenizer(r));
317 if let Ok(runtime) = &usable {
320 self.usable.lock().expect("runtime catalog").insert(id, runtime.clone());
321 }
322 Found { id, worker: Some(worker), info: Some(info), usable }
323 }
324
325 fn with_tokenizer(&self, mut runtime: Runtime) -> Result<Runtime, String> {
326 let linked = crate::linked::get(runtime.id.base()).is_some();
327 match runtime.info.tokenizer.clone() {
328 Some(TokenizerSource::Library(stem)) => {
329 runtime.tokenizer = crate::dylib_tokenizer::find_library(&runtime.worker.bin, &stem);
330 if runtime.tokenizer.is_none() && !linked {
331 return Err(format!(
332 "the {} runtime at {} ships no tokenizer library ({} beside it or in ../lib)",
333 runtime.id,
334 runtime.worker.bin.display(),
335 crate::dylib_tokenizer::library_file(&stem)
336 ));
337 }
338 }
339 Some(TokenizerSource::HuggingFace) => {}
340 Some(TokenizerSource::Engine) if linked => {}
341 Some(TokenizerSource::Engine) => {
342 return Err(format!(
343 "only the {id} runtime's engine tokenizes its artifacts, and this superfluid does not link it \
344 (a source build adds it with --features {feature})",
345 id = runtime.id,
346 feature = crate::linked::feature_for(runtime.id.base().name()).unwrap_or("<the runtime>"),
347 ))
348 }
349 None => {}
350 }
351 Ok(runtime)
352 }
353
354 fn missing(&self, id: RuntimeId) -> String {
355 if let Some(install) = id.install() {
356 let have: Vec<String> = crate::installs::list(self, id).into_iter().map(|i| i.name).collect();
357 return format!(
358 "{} has no install {install} here (installed: {})",
359 id.base(),
360 if have.is_empty() { "none".to_string() } else { have.join(", ") }
361 );
362 }
363 let shipped = self.shipped_ids();
364 if !shipped.is_empty() {
365 return format!(
366 "the {id} runtime is not here: this superfluid ships no adapter for it (it ships: {}), and none is installed in {}",
367 shipped.iter().map(|i| i.name()).collect::<Vec<_>>().join(", "),
368 self.package_dir(id).display()
369 );
370 }
371 format!(
372 "the {id} runtime is not installed: no {w} in {pkg} or beside superfluid \
373 (install it with `superfluid runtime install {id}`, or build {w} with `cargo build -p superfluid-adapter-{id}`)",
374 w = worker_name(id),
375 pkg = self.package_dir(id).join("current").join("bin").display(),
376 )
377 }
378
379 pub fn survey(&self) -> Vec<Found> {
380 self.ids().into_iter().map(|id| self.inspect(id)).collect()
381 }
382
383 pub fn named(&self, name: &str) -> Result<RuntimeId, String> {
384 let name = name.trim();
385 if let Some((base, install)) = name.split_once('@') {
386 let id = self.named(base)?;
387 let at = RuntimeId::parse(&format!("{id}@{install}")).ok_or_else(|| format!("'{install}' is not an install's name"))?;
388 if crate::installs::list(self, id).iter().any(|i| i.name == install) {
389 return Ok(at);
390 }
391 return Err(self.missing(at));
392 }
393 let ids = self.ids();
394 if let Some(id) = ids.iter().copied().find(|id| id.name() == name) {
395 return Ok(id);
396 }
397 for found in self.survey() {
398 if found.info.is_some_and(|i| i.aliases.iter().any(|a| a == name)) {
399 return Ok(found.id);
400 }
401 }
402 Err(format!("no runtime '{name}' here"))
403 }
404
405 pub fn reads(&self, id: RuntimeId, path: &Path) -> Result<(), String> {
406 let found = self.inspect(id);
407 let Some(info) = &found.info else {
408 return found.usable.map(|_| ());
409 };
410 if info.format_of(path).is_some() {
411 return Ok(());
412 }
413 let mut why = format!("runtime {id} cannot serve {}: it reads {}", path.display(), info.reads_described());
414 match self.readers(path).first() {
415 Some((other, format)) => why.push_str(&format!(
416 ", and this is {} (pass --runtime {other}, or convert the model)",
417 format.describe
418 )),
419 None => why.push_str(", and no runtime here reads this file"),
420 }
421 Err(why)
422 }
423
424 pub fn readers(&self, path: &Path) -> Vec<(RuntimeId, Format)> {
425 let mut known = self.known_readers(path);
426 if known.is_empty() {
427 let found = self.survey();
428 let cannot_run = |id: &RuntimeId| found.iter().any(|f| f.id == *id && f.usable.is_err());
429 known = readers_in(&found, path);
430 known.sort_by_key(|(id, _)| (cannot_run(id), *id));
431 return known;
432 }
433 known
434 }
435
436 fn known_readers(&self, path: &Path) -> Vec<(RuntimeId, Format)> {
437 let mut known: Vec<(RuntimeId, Format)> = self
438 .usable
439 .lock()
440 .expect("runtime catalog")
441 .values()
442 .filter(|r| r.id.install().is_none())
443 .filter_map(|r| Some((r.id, r.info.format_of(path)?.clone())))
444 .collect();
445 known.sort_by_key(|(id, _)| *id);
446 known
447 }
448
449 pub fn reader_of(&self, path: &Path) -> Result<RuntimeId, String> {
450 if let Some((id, _)) = self.known_readers(path).first() {
451 return Ok(*id);
452 }
453 let mut cannot_run = None;
454 for id in self.ids() {
455 let found = self.inspect(id);
456 if found.info.as_ref().and_then(|info| info.format_of(path)).is_none() {
457 continue;
458 }
459 match found.usable {
460 Ok(runtime) => {
461 self.usable.lock().expect("runtime catalog").insert(id, runtime);
462 return Ok(id);
463 }
464 Err(_) => {
465 cannot_run.get_or_insert(id);
466 }
467 }
468 }
469 if let Some(id) = cannot_run {
470 return Ok(id);
471 }
472 Err(format!(
473 "no runtime here reads {} ({}); `superfluid runtimes {}` says more, and `superfluid runtime install <id>` adds a runtime",
474 path.display(),
475 self.here_described(),
476 path.display()
477 ))
478 }
479
480 pub fn not_here(&self, name: &str, path: &Path, why: &str) -> String {
481 let mut msg = format!("{why} to serve {} ({})", path.display(), self.here_described());
482 if let Some((other, format)) = self.readers(path).first() {
483 msg.push_str(&format!("; it is {}, which {other} here reads: pass --runtime {other}", format.describe));
484 }
485 let runtime = name.split('@').next().unwrap_or(name);
486 msg.push_str(&format!("; `superfluid runtime install {runtime}` installs {runtime}"));
487 msg
488 }
489
490 pub fn here_described(&self) -> String {
491 let all = self.survey();
492 if all.is_empty() {
493 return "no runtime is installed".to_string();
494 }
495 let parts: Vec<String> = all
496 .iter()
497 .map(|f| {
498 let state = if f.usable.is_ok() { "" } else { " (not usable)" };
499 match &f.info {
500 Some(info) => format!("{}{state} reads {}", f.id, info.reads_described()),
501 None => format!("{}{state}", f.id),
502 }
503 })
504 .collect();
505 format!("runtimes here: {}", parts.join("; "))
506 }
507}
508
509fn repair_command(id: RuntimeId) -> String {
510 match id.install() {
511 Some(install) => format!("superfluid runtime repair {} {install}", id.base()),
512 None => format!("superfluid runtime repair {id}"),
513 }
514}
515
516pub fn readers_in(found: &[Found], path: &Path) -> Vec<(RuntimeId, Format)> {
517 found
518 .iter()
519 .filter_map(|f| {
520 let format = f.info.as_ref()?.format_of(path)?.clone();
521 Some((f.id, format))
522 })
523 .collect()
524}
525
526fn workers_in(dir: &Path) -> Vec<RuntimeId> {
527 std::fs::read_dir(dir)
528 .map(|rd| {
529 rd.flatten()
530 .filter(|e| is_executable(&e.path()))
531 .filter_map(|e| e.file_name().to_str().and_then(|n| n.strip_prefix("superfluid-worker-")).and_then(RuntimeId::parse))
532 .collect()
533 })
534 .unwrap_or_default()
535}
536
537fn is_executable(p: &Path) -> bool {
538 use std::os::unix::fs::PermissionsExt;
539 std::fs::metadata(p).is_ok_and(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
540}
541
542pub fn check_worker(id: RuntimeId, worker: &Worker) -> Result<Runtime, String> {
543 let report = run_check(worker)?;
544 runtime_from_check(id, worker.clone(), &report)
545}
546
547fn run_check(w: &Worker) -> Result<Value, String> {
548 run_check_model(w, None)
549}
550
551pub fn run_check_model(w: &Worker, model: Option<&Path>) -> Result<Value, String> {
552 let mut cmd = Command::new(&w.bin);
553 crate::exec::without_secrets(&mut cmd).arg("check").args(&w.prefix);
554 if let Some(m) = model {
555 cmd.arg("--model").arg(m);
556 }
557 cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped());
558 let child = crate::exec::when_not_busy(|| cmd.spawn()).map_err(|e| format!("{} could not start: {e}", w.bin.display()))?;
559 let pid = child.id() as i32;
560 let (tx, rx) = std::sync::mpsc::channel();
561 std::thread::spawn(move || {
562 let _ = tx.send(child.wait_with_output());
563 });
564 let out = match rx.recv_timeout(CHECK_TIMEOUT) {
565 Ok(out) => out.map_err(|e| format!("{} check: {e}", w.bin.display()))?,
566 Err(_) => {
567 kill_hard(pid);
568 return Err(format!("{} check did not answer within {}s", w.bin.display(), CHECK_TIMEOUT.as_secs()));
569 }
570 };
571 serde_json::from_slice::<Value>(&out.stdout).map_err(|_| {
572 let stderr = String::from_utf8_lossy(&out.stderr);
573 let tail: Vec<&str> = stderr.lines().rev().take(3).collect();
574 format!(
575 "{} check failed ({}){}",
576 w.bin.display(),
577 out.status,
578 if tail.is_empty() {
579 String::new()
580 } else {
581 format!(": {}", tail.into_iter().rev().collect::<Vec<_>>().join(" / "))
582 }
583 )
584 })
585}
586
587pub fn sizing_of(rt: &Runtime, model: &Path) -> Result<superfluid_adapter_kit::Sizing, String> {
588 let report = run_check_model(&rt.worker, Some(model))?;
589 match &report["model"]["sizing"] {
590 Value::Null => Err(format!("the {} runtime sizes no context window here", rt.id.base().name())),
591 Value::String(why) => Err(why.clone()),
592 said => superfluid_adapter_kit::Sizing::from_json(said)
593 .ok_or_else(|| format!("{} check reported a sizing this superfluid does not read: {said}", rt.worker.bin.display())),
594 }
595}
596
597fn runtime_from_check(id: RuntimeId, worker: Worker, report: &Value) -> Result<Runtime, String> {
598 let reported = report["runtime"]["id"].as_str().unwrap_or_default();
599 if reported != id.base().name() {
600 return Err(format!("{} is not the {id} runtime's worker (it reports '{reported}')", worker.bin.display()));
601 }
602 let speaks: Vec<u64> = report["link_w"].as_array().map(|a| a.iter().filter_map(Value::as_u64).collect()).unwrap_or_default();
603 let ours = u64::from(superfluid_agent::client::PROTO_VERSION);
604 if !speaks.contains(&ours) {
605 let fix = if worker.source == Source::Installed && speaks.iter().all(|v| *v < ours) {
606 format!("`{}` installs it again with this superfluid's worker", repair_command(id))
607 } else {
608 "update the one that is older".to_string()
609 };
610 return Err(format!(
611 "the {id} runtime at {} speaks Link W {speaks:?} and this superfluid speaks {ours}: {fix}",
612 worker.bin.display()
613 ));
614 }
615 match &report["available"] {
616 Value::Bool(true) => {}
617 Value::String(why) if worker.source == Source::Shipped => {
618 return Err(format!("the {id} runtime is not installed: {why}"));
619 }
620 Value::String(why) => {
621 return Err(format!(
622 "the {id} runtime at {} cannot run here: {why}{}",
623 worker.bin.display(),
624 if worker.source == Source::Installed {
625 format!(" (`{}` reinstalls it)", repair_command(id))
626 } else {
627 String::new()
628 }
629 ))
630 }
631 other => return Err(format!("{} check reported availability {other}", worker.bin.display())),
632 }
633 Ok(Runtime {
634 id,
635 version: report["runtime"]["version"].as_str().map(str::to_string),
636 capabilities: report.get("capabilities").filter(|c| c.as_object().is_some_and(|o| !o.is_empty())).map(Value::to_string),
637 info: Info::from_report(report),
638 devices: superfluid_worker::check::devices_from(&report["devices"]),
639 tokenizer: None,
640 pull: report.get("pull").map(|p| {
641 p["options"]
642 .as_array()
643 .map(|a| a.iter().filter_map(|o| Some((o["name"].as_str()?.to_string(), o["value"].as_bool().unwrap_or(false)))).collect())
644 .unwrap_or_default()
645 }),
646 worker,
647 })
648}
649
650fn kill_hard(pid: i32) {
651 extern "C" {
652 fn kill(pid: i32, sig: i32) -> i32;
653 }
654 unsafe {
656 kill(pid, 9);
657 }
658}
659
660#[cfg(test)]
661pub(crate) mod tests {
662 use super::*;
663 use std::os::unix::fs::PermissionsExt;
664
665 fn scratch(tag: &str) -> PathBuf {
666 let d = std::env::temp_dir().join(format!("superfluid-runtimes-{tag}-{}", std::process::id()));
667 let _ = std::fs::remove_dir_all(&d);
668 std::fs::create_dir_all(&d).unwrap();
669 d
670 }
671
672 pub(crate) fn fake_worker(dir: &Path, name: &str, report: &str, exit: i32) -> PathBuf {
673 let p = dir.join(name);
674 std::fs::write(&p, format!("#!/bin/sh\ncat <<'EOF'\n{report}\nEOF\nexit {exit}\n")).unwrap();
675 std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).unwrap();
676 p
677 }
678
679 pub(crate) fn report(id: &str, link_w: u16, available: &str) -> String {
680 let (aliases, formats, tokenizer) = match id {
681 "llamacpp" => (
682 r#"["llama.cpp"]"#,
683 r#"[{"id":"gguf","describe":"a GGUF file","file":{"extensions":["gguf"],"magic":"GGUF"}}]"#,
684 r#"{"from":"library","name":"superfluid_tokenizer_llamacpp"}"#,
685 ),
686 "mlx" => (
687 "[]",
688 r#"[{"id":"mlx","describe":"an MLX model directory","directory":{"files":["config.json"],"extensions":["safetensors"]}}]"#,
689 r#"{"from":"huggingface"}"#,
690 ),
691 _ => (
692 r#"["native"]"#,
693 r#"[{"id":"base","describe":"a .base bundle","file":{"extensions":["base"],"magic":"BASE"}}]"#,
694 r#"{"from":"engine"}"#,
695 ),
696 };
697 format!(
698 r#"{{"runtime":{{"id":"{id}","version":"v1","aliases":{aliases}}},"link_w":[{link_w}],"available":{available},"formats":{formats},"tokenizer":{tokenizer},"devices":[{{"backend":"Metal","name":"Apple M5 Pro","memory":36000000000}}],"capabilities":{{"descriptor_version":1,"load":{{"kv_bits":"no such knob"}},"serving":{{"park_lossy":"no lossy encoding"}}}}}}"#
699 )
700 }
701
702 const V: u16 = superfluid_agent::client::PROTO_VERSION;
703
704 pub(crate) fn toy_report(id: &str, available: &str) -> String {
705 format!(
706 r#"{{"runtime":{{"id":"{id}","version":"v1"}},"link_w":[{V}],"available":{available},"formats":[{{"id":"toy","describe":"a toy file","file":{{"extensions":["toy"]}}}}],"tokenizer":{{"from":"huggingface"}}}}"#
707 )
708 }
709
710 fn catalog(home: &Path) -> Catalog {
711 Catalog::new(home.to_path_buf(), None, home.join("superfluid-workerd"))
712 }
713
714 fn install_fake(home: &Path, id: &str, report_text: &str, exit: i32) -> PathBuf {
715 let bin_dir = home.join("runtimes").join(id).join("current/bin");
716 std::fs::create_dir_all(&bin_dir).unwrap();
717 fake_worker(&bin_dir, &format!("superfluid-worker-{id}"), report_text, exit)
718 }
719
720 #[test]
721 fn a_models_sizing_is_read_from_its_runtimes_check() {
722 let dir = scratch("sizing");
723 let with_model = |model: &str| {
724 let r = report("llamacpp", V, "true");
725 format!("{},\"model\":{model}}}", &r[..r.len() - 1])
726 };
727 let rt = |name: &str, text: &str| Runtime {
728 id: RuntimeId::new("llamacpp"),
729 worker: Worker { bin: fake_worker(&dir, name, text, 0), prefix: Vec::new(), source: Source::Env },
730 version: None,
731 capabilities: None,
732 info: Info::default(),
733 devices: Vec::new(),
734 tokenizer: None,
735 pull: None,
736 };
737 let model = Path::new("/m.gguf");
738 let figures = r#"{"budget_bytes":34000000000,"weight_bytes":600000000,"kv_bytes_per_token":114688,"trained_context":40960}"#;
739 let sized = rt("sized", &with_model(&format!(r#"{{"path":"/m.gguf","reads":true,"sizing":{figures}}}"#)));
740 assert_eq!(
741 sizing_of(&sized, model),
742 Ok(superfluid_adapter_kit::Sizing {
743 budget_bytes: 34_000_000_000,
744 weight_bytes: 600_000_000,
745 kv_bytes_per_token: 114_688,
746 trained_context: 40_960
747 })
748 );
749 let declined = rt(
750 "declined",
751 &with_model(r#"{"path":"/m.gguf","reads":true,"sizing":"the llamacpp runtime sizes no window on Vulkan yet"}"#),
752 );
753 assert_eq!(sizing_of(&declined, model), Err("the llamacpp runtime sizes no window on Vulkan yet".to_string()));
754 let silent = rt("silent", &with_model(r#"{"path":"/m.gguf","reads":true}"#));
755 assert_eq!(sizing_of(&silent, model), Err("the llamacpp runtime sizes no context window here".to_string()));
756 let garbled = rt("garbled", &with_model(r#"{"path":"/m.gguf","reads":true,"sizing":{"budget_bytes":1}}"#));
757 assert!(sizing_of(&garbled, model).unwrap_err().contains("reported a sizing this superfluid does not read"));
758 }
759
760 #[test]
761 fn an_installed_package_is_found_checked_and_remembered() {
762 let llamacpp = RuntimeId::new("llamacpp");
763 if crate::linked::get(llamacpp).is_some() {
764 return;
765 }
766 let home = scratch("installed");
767 let w = install_fake(&home, "llamacpp", &report("llamacpp", V, "true"), 0);
768 let cat = catalog(&home);
769 let found: Vec<RuntimeId> = cat.ids().into_iter().filter(|i| crate::linked::get(*i).is_none()).collect();
770 assert_eq!(found, vec![llamacpp]);
771 let e = cat.require(llamacpp).unwrap_err();
772 assert!(e.ends_with("ships no tokenizer library (libsuperfluid_tokenizer_llamacpp.dylib beside it or in ../lib)")
773 || e.ends_with("ships no tokenizer library (libsuperfluid_tokenizer_llamacpp.so beside it or in ../lib)"), "{e}");
774 let lib_dir = home.join("runtimes/llamacpp/current/lib");
775 std::fs::create_dir_all(&lib_dir).unwrap();
776 let lib = lib_dir.join(crate::dylib_tokenizer::library_file("superfluid_tokenizer_llamacpp"));
777 std::fs::write(&lib, b"").unwrap();
778 let rt = cat.require(llamacpp).unwrap();
779 let pinned = w.canonicalize().unwrap();
780 assert_eq!(rt.worker, Worker { bin: pinned, prefix: vec![], source: Source::Installed });
781 assert_eq!(rt.version.as_deref(), Some("v1"));
782 assert_eq!(rt.info.aliases, ["llama.cpp"]);
783 assert_eq!(rt.device().unwrap().summary(), "Metal: Apple M5 Pro, 36.0 GB");
784 assert_eq!(rt.tokenizer.as_deref().map(|p| p.canonicalize().unwrap()), Some(lib.canonicalize().unwrap()));
785 assert_eq!(rt.static_capabilities().check_startup_flags(true, None, None).unwrap_err(), "--park-lossy: no lossy encoding");
786 let args = rt.serve_args(Path::new("/m.gguf"), 4096, 4, 8);
787 assert_eq!(args, ["--model", "/m.gguf", "--max-context", "4096", "--max-batch", "4"], "its record refuses --kv-bits");
788 }
789
790 #[test]
791 fn the_home_is_never_relative_to_the_working_directory() {
792 assert_eq!(home_from(Some("/srv/superfluid".into()), Some("/home/u".into())), PathBuf::from("/srv/superfluid"));
793 assert_eq!(home_from(None, Some("/home/u".into())), PathBuf::from("/home/u/.superfluid"));
794 assert_eq!(home_from(Some("".into()), Some("/home/u".into())), PathBuf::from("/home/u/.superfluid"), "an empty variable is unset");
795 assert_eq!(home_from(None, None), PathBuf::from("/.superfluid"));
796 assert_eq!(home_from(None, Some("".into())), PathBuf::from("/.superfluid"));
797 assert_eq!(home_from(None, Some(".".into())), PathBuf::from("/.superfluid"));
798 }
799
800 fn counting_worker(dir: &Path, name: &str, report: &str, tally: &Path) -> PathBuf {
801 std::fs::create_dir_all(dir).unwrap();
802 let p = dir.join(name);
803 std::fs::write(&p, format!("#!/bin/sh\necho run >> '{}'\ncat <<'EOF'\n{report}\nEOF\n", tally.display())).unwrap();
804 std::fs::set_permissions(&p, std::fs::Permissions::from_mode(0o755)).unwrap();
805 p
806 }
807
808 fn runs(tally: &Path) -> usize {
809 std::fs::read_to_string(tally).map(|t| t.lines().count()).unwrap_or(0)
810 }
811
812 #[test]
813 fn a_remembered_runtime_whose_worker_is_gone_is_looked_for_again() {
814 let home = scratch("regone");
815 let toyrt = RuntimeId::new("toyrt");
816 let tally = home.join("tally");
817 let pkg = home.join("runtimes/toyrt");
818 counting_worker(&pkg.join("b1/bin"), "superfluid-worker-toyrt", &toy_report("toyrt", "true"), &tally);
819 std::os::unix::fs::symlink("b1", pkg.join("current")).unwrap();
820 let cat = catalog(&home);
821 let first = cat.require(toyrt).unwrap();
822 assert!(cat.require(toyrt).is_ok());
823 assert_eq!(runs(&tally), 1, "a usable runtime is checked once");
824 counting_worker(&pkg.join("b2/bin"), "superfluid-worker-toyrt", &toy_report("toyrt", "true"), &tally);
825 std::fs::remove_file(pkg.join("current")).unwrap();
826 std::os::unix::fs::symlink("b2", pkg.join("current")).unwrap();
827 assert_eq!(cat.require(toyrt).unwrap().worker, first.worker);
828 assert_eq!(runs(&tally), 1);
829 std::fs::remove_dir_all(pkg.join("b1")).unwrap();
830 let again = cat.require(toyrt).unwrap();
831 assert_eq!(again.worker.bin, pkg.join("b2/bin/superfluid-worker-toyrt").canonicalize().unwrap());
832 assert_eq!(runs(&tally), 2);
833 std::fs::remove_dir_all(pkg.join("b2")).unwrap();
834 let e = cat.require(toyrt).unwrap_err();
835 assert!(e.starts_with("the toyrt runtime is not installed"), "{e}");
836 }
837
838 #[test]
839 fn a_model_asks_no_runtime_past_the_first_that_serves_it() {
840 let home = scratch("lazy");
841 let (aart, zzrt) = (RuntimeId::new("aart"), RuntimeId::new("zzrt"));
842 let (asked_a, asked_z) = (home.join("asked-a"), home.join("asked-z"));
843 counting_worker(&home.join("runtimes/aart/current/bin"), "superfluid-worker-aart", &toy_report("aart", "true"), &asked_a);
844 let reads_zz = toy_report("zzrt", "true").replace(r#""extensions":["toy"]"#, r#""extensions":["zz"]"#);
845 counting_worker(&home.join("runtimes/zzrt/current/bin"), "superfluid-worker-zzrt", &reads_zz, &asked_z);
846 let cat = catalog(&home);
847 let toy = home.join("m.toy");
848 std::fs::write(&toy, b"toy").unwrap();
849 assert_eq!(cat.reader_of(&toy).unwrap(), aart);
850 assert_eq!((runs(&asked_a), runs(&asked_z)), (1, 0), "the runtime after the reader was not run");
851 assert!(cat.require(aart).is_ok());
852 assert_eq!(runs(&asked_a), 1, "and the reader was not checked a second time");
853 let zz = home.join("m.zz");
854 std::fs::write(&zz, b"zz").unwrap();
855 assert_eq!(cat.reader_of(&zz).unwrap(), zzrt);
856 assert_eq!(runs(&asked_z), 1);
857 }
858
859 #[test]
860 fn a_pick_of_one_install_leaves_later_models_on_the_default() {
861 let home = scratch("pinned-reader");
862 let toyrt = RuntimeId::new("toyrt");
863 let pkg = home.join("runtimes/toyrt");
864 for b in ["b1", "b2"] {
865 let bin = pkg.join(b).join("bin");
866 std::fs::create_dir_all(&bin).unwrap();
867 fake_worker(&bin, "superfluid-worker-toyrt", &toy_report("toyrt", "true"), 0);
868 }
869 std::os::unix::fs::symlink("b2", pkg.join("current")).unwrap();
870 let cat = catalog(&home);
871 let pinned = cat.require(RuntimeId::parse("toyrt@b1").unwrap()).unwrap();
872 assert_eq!(pinned.worker.bin, pkg.join("b1/bin/superfluid-worker-toyrt").canonicalize().unwrap());
873 let model = home.join("m.toy");
874 std::fs::write(&model, b"toy").unwrap();
875 assert_eq!(cat.reader_of(&model).unwrap(), toyrt, "the runtime, not the install another model picked");
876 assert_eq!(cat.readers(&model).into_iter().map(|(id, _)| id).collect::<Vec<_>>(), [toyrt]);
877 let served = cat.require(toyrt).unwrap();
878 assert_eq!(served.worker.bin, pkg.join("b2/bin/superfluid-worker-toyrt").canonicalize().unwrap());
879 }
880
881 #[test]
882 fn an_adapter_a_release_ships_is_known_before_its_runtime_is_installed() {
883 for flat in [false, true] {
884 let root = scratch(if flat { "shipped-flat" } else { "shipped-sdk" });
885 let beside = if flat { root.clone() } else { root.join("bin") };
886 let libexec = root.join("libexec").join("superfluid");
887 std::fs::create_dir_all(&beside).unwrap();
888 std::fs::create_dir_all(&libexec).unwrap();
889 let not_found = r#""toyrt not found: install it with `superfluid runtime install toyrt`""#;
890 let adapter = fake_worker(&libexec, "superfluid-worker-toyrt", &toy_report("toyrt", not_found), 1).canonicalize().unwrap();
891 std::fs::write(libexec.join("libsuperfluid_tokenizer_toyrt.dylib"), b"").unwrap();
892 let home = root.join("home");
893 let catalog = || Catalog::new(home.clone(), Some(beside.clone()), beside.join("superfluid-workerd"));
894 let (toyrt, vllm) = (RuntimeId::new("toyrt"), RuntimeId::new("vllm"));
895
896 let cat = catalog();
897 assert_eq!(cat.shipped_ids(), vec![toyrt], "flat: {flat}");
898 assert!(cat.ids().contains(&toyrt));
899 assert_eq!(cat.named("toyrt").unwrap(), toyrt);
900 assert_eq!(cat.locate(toyrt).unwrap(), Worker { bin: adapter.clone(), prefix: vec![], source: Source::Shipped });
901 assert_eq!(
902 cat.require(toyrt).unwrap_err(),
903 "the toyrt runtime is not installed: toyrt not found: install it with `superfluid runtime install toyrt`"
904 );
905 let model = root.join("m.toy");
906 std::fs::write(&model, b"toy").unwrap();
907 assert_eq!(cat.reader_of(&model).unwrap(), toyrt);
908 assert_eq!(cat.reads(toyrt, &model), Ok(()));
909 assert_eq!(crate::installs::installer(&cat, toyrt).unwrap(), adapter);
910 let e = cat.require(vllm).unwrap_err();
911 assert!(e.starts_with("the vllm runtime is not here: this superfluid ships no adapter for it (it ships: toyrt)"), "{e}");
912 assert_eq!(
913 crate::installs::installer(&cat, vllm).unwrap_err(),
914 "this superfluid ships no adapter to install the vllm runtime with (it ships: toyrt)"
915 );
916
917 let installed = install_fake(&home, "toyrt", &toy_report("toyrt", "true"), 0).canonicalize().unwrap();
918 let cat = catalog();
919 assert_eq!(cat.locate(toyrt).unwrap(), Worker { bin: installed, prefix: vec![], source: Source::Installed });
920 assert!(cat.require(toyrt).is_ok());
921 assert_eq!(crate::installs::installer(&cat, toyrt).unwrap(), adapter);
922 }
923 }
924
925 #[test]
926 fn a_source_build_ships_no_adapter() {
927 let beside = scratch("no-libexec");
928 let cat = Catalog::new(scratch("no-libexec-home"), Some(beside), PathBuf::from("/nonexistent/superfluid-workerd"));
929 let vllm = RuntimeId::new("vllm");
930 assert!(cat.shipped_ids().is_empty());
931 assert_eq!(cat.shipped(vllm), None);
932 let e = cat.require(vllm).unwrap_err();
933 assert!(e.contains("cargo build -p superfluid-adapter-vllm"), "{e}");
934 let e = crate::installs::installer(&cat, vllm).unwrap_err();
935 assert!(e.starts_with("no superfluid-worker-vllm to install the vllm runtime with") && e.contains("cargo build"), "{e}");
936 }
937
938 #[test]
939 fn a_worker_beside_superfluid_wins_over_an_installed_one() {
940 let vllm = RuntimeId::new("vllm");
941 let home = scratch("beside-home");
942 let beside = scratch("beside-exe");
943 install_fake(&home, "vllm", &report("vllm", V, "true"), 0);
944 let mine = fake_worker(&beside, "superfluid-worker-vllm", &report("vllm", V, "true"), 0);
945 std::fs::write(beside.join("superfluid-worker-vllm.d"), b"").unwrap();
946 let cat = Catalog::new(home, Some(beside), PathBuf::from("/nonexistent/superfluid-workerd"));
947 assert_eq!(cat.locate(vllm).unwrap(), Worker { bin: mine, prefix: vec![], source: Source::BesideDaemon });
948 assert!(cat.ids().contains(&vllm) && !cat.ids().iter().any(|i| i.name().ends_with(".d")));
949 }
950
951 #[test]
952 fn a_runtime_this_build_links_is_served_by_this_build() {
953 let beside = scratch("linked-beside");
954 let workerd = beside.join("superfluid-workerd");
955 for id in crate::linked::ids() {
956 fake_worker(&beside, &worker_name(id), &report(id.name(), V, "true"), 0);
957 let cat = Catalog::new(scratch("linked-home"), Some(beside.clone()), workerd.clone());
958 let worker = cat.locate(id).unwrap();
959 assert_eq!(worker, Worker { bin: workerd.clone(), prefix: vec!["--engine".into(), id.name().into()], source: Source::BuiltIn });
960 }
961 }
962
963 #[test]
964 fn a_program_named_as_the_workerd_is_started_as_one() {
965 let program = scratch("embedder").join("embedding-program");
966 let cat = Catalog::with_workerd(program.clone());
967 assert_eq!(cat.workerd(), program.as_path());
968 for id in crate::linked::ids() {
969 let worker = cat.locate(id).unwrap();
970 assert_eq!(worker.bin, program);
971 let serve: Vec<String> = worker.prefix.iter().cloned().chain(["--model".into(), "m".into()]).collect();
975 assert!(crate::workerd::is_invocation(&serve), "{serve:?}");
976 let check: Vec<String> = std::iter::once("check".to_string()).chain(worker.prefix.iter().cloned()).collect();
977 assert!(crate::workerd::is_invocation(&check), "{check:?}");
978 }
979 }
980
981 #[test]
982 fn what_is_missing_or_broken_says_why_and_how_to_fix_it() {
983 let mlx = RuntimeId::new("mlx");
984 if crate::linked::get(mlx).is_some() {
985 return;
986 }
987 let home = scratch("broken");
988 let cat = catalog(&home);
989 let e = cat.require(mlx).unwrap_err();
990 assert!(e.starts_with("the mlx runtime is not installed: no superfluid-worker-mlx in "), "{e}");
991 assert!(e.contains("`superfluid runtime install mlx`") && e.contains("cargo build -p superfluid-adapter-mlx"), "{e}");
992
993 let w = install_fake(&home, "mlx", &report("mlx", V, r#""ModuleNotFoundError: No module named 'mlx'""#), 1);
994 let e = cat.require(mlx).unwrap_err();
995 let pinned = w.canonicalize().unwrap();
996 assert_eq!(
997 e,
998 format!(
999 "the mlx runtime at {} cannot run here: ModuleNotFoundError: No module named 'mlx' (`superfluid runtime repair mlx` reinstalls it)",
1000 pinned.display()
1001 )
1002 );
1003 let found = cat.inspect(mlx);
1004 assert_eq!(found.info.unwrap().formats[0].id, "mlx");
1005
1006 install_fake(&home, "mlx", &report("mlx", V + 1, "true"), 0);
1007 let e = cat.require(mlx).unwrap_err();
1008 assert!(e.contains(&format!("speaks Link W [{}] and this superfluid speaks {V}", V + 1)), "{e}");
1009 assert!(e.ends_with("update the one that is older"), "a newer worker: superfluid is the older one: {e}");
1010 install_fake(&home, "mlx", &report("mlx", V - 1, "true"), 0);
1011 let e = cat.require(mlx).unwrap_err();
1012 assert!(e.contains(&format!("speaks Link W [{}] and this superfluid speaks {V}", V - 1)), "{e}");
1013 assert!(e.ends_with("`superfluid runtime repair mlx` installs it again with this superfluid's worker"), "{e}");
1014 let old = home.join("runtimes/mlx/old/bin");
1015 std::fs::create_dir_all(&old).unwrap();
1016 fake_worker(&old, "superfluid-worker-mlx", &report("mlx", V - 1, "true"), 0);
1017 let e = cat.require(RuntimeId::parse("mlx@old").unwrap()).unwrap_err();
1018 assert!(e.ends_with("`superfluid runtime repair mlx old` installs it again with this superfluid's worker"), "{e}");
1019 fake_worker(&old, "superfluid-worker-mlx", &report("mlx", V, r#""no Metal device""#), 1);
1020 let e = cat.require(RuntimeId::parse("mlx@old").unwrap()).unwrap_err();
1021 assert!(e.ends_with("cannot run here: no Metal device (`superfluid runtime repair mlx old` reinstalls it)"), "{e}");
1022 let e = cat.not_here("mlx@gone", Path::new("/m"), "mlx has no install gone here");
1023 assert!(e.ends_with("`superfluid runtime install mlx` installs mlx"), "{e}");
1024
1025 install_fake(&home, "mlx", &report("llamacpp", V, "true"), 0);
1026 assert!(cat.require(mlx).unwrap_err().contains("is not the mlx runtime's worker"));
1027
1028 std::fs::write(&w, "#!/bin/sh\necho 'dyld: Library not loaded: libpython3.12.dylib' >&2\nexit 134\n").unwrap();
1029 let e = cat.require(mlx).unwrap_err();
1030 assert!(e.contains("check failed") && e.ends_with("dyld: Library not loaded: libpython3.12.dylib"), "{e}");
1031
1032 install_fake(&home, "mlx", &report("mlx", V, "true"), 0);
1033 assert_eq!(cat.require(mlx).unwrap().version.as_deref(), Some("v1"));
1034 }
1035
1036 #[test]
1037 fn a_worker_that_declares_no_formats_is_named_as_older() {
1038 let old = RuntimeId::new("oldrt");
1039 let home = scratch("old");
1040 install_fake(&home, "oldrt", &format!(r#"{{"runtime":{{"id":"oldrt","version":"v0"}},"link_w":[{V}],"available":true}}"#), 0);
1041 let cat = catalog(&home);
1042 assert!(cat.inspect(old).info.unwrap().formats.is_empty());
1043 let file = scratch("old-model").join("m.weights");
1044 std::fs::write(&file, b"????").unwrap();
1045 let e = cat.reader_of(&file).unwrap_err();
1046 assert!(e.contains("oldrt reads no format: its worker declares none, so it is older than this superfluid (rebuild or update it)"), "{e}");
1047 }
1048
1049 #[test]
1050 fn a_model_two_runtimes_read_goes_to_the_one_that_can_run() {
1051 let (broken, working) = (RuntimeId::new("abroken"), RuntimeId::new("zworking"));
1052 let toy = |id: &str, available: &str| {
1053 format!(
1054 r#"{{"runtime":{{"id":"{id}","version":"v1"}},"link_w":[{V}],"available":{available},"formats":[{{"id":"toy","describe":"a toy file","file":{{"extensions":["toy"]}}}}],"tokenizer":{{"from":"huggingface"}}}}"#
1055 )
1056 };
1057 let home = scratch("two-readers");
1058 install_fake(&home, "abroken", &toy("abroken", r#""library missing""#), 1);
1059 install_fake(&home, "zworking", &toy("zworking", "true"), 0);
1060 let file = scratch("two-readers-model").join("m.toy");
1061 std::fs::write(&file, b"toy").unwrap();
1062
1063 let cat = catalog(&home);
1064 let listed: Vec<RuntimeId> = cat.readers(&file).into_iter().map(|(id, _)| id).collect();
1065 assert_eq!(listed, vec![working, broken], "the one that cannot run stays listed, after the one that can");
1066 assert_eq!(cat.reader_of(&file).unwrap(), working, "the reader that can run, not the first by name");
1067 assert_eq!(crate::runtime_pick::RuntimePick::default().resolve(&file, &cat).unwrap(), working);
1068 assert!(cat.require(working).is_ok());
1069 let fresh = catalog(&home);
1070 let e = fresh.not_here("vllm", &file, "no runtime 'vllm' here");
1071 assert!(e.contains("which zworking here reads: pass --runtime zworking"), "{e}");
1072
1073 let alone = scratch("one-broken-reader");
1074 install_fake(&alone, "abroken", &toy("abroken", r#""library missing""#), 1);
1075 let cat = catalog(&alone);
1076 assert_eq!(cat.reader_of(&file).unwrap(), broken);
1077 let e = cat.require(broken).unwrap_err();
1078 assert!(e.contains("cannot run here: library missing"), "{e}");
1079 }
1080
1081 #[test]
1082 fn a_runtime_is_named_by_its_id_or_an_alias_it_declares() {
1083 if crate::linked::get(RuntimeId::new("llamacpp")).is_some() {
1084 return;
1085 }
1086 let home = scratch("named");
1087 install_fake(&home, "llamacpp", &report("llamacpp", V, "true"), 0);
1088 let cat = catalog(&home);
1089 assert_eq!(cat.named("llamacpp").unwrap(), RuntimeId::new("llamacpp"));
1090 assert_eq!(cat.named("llama.cpp").unwrap(), RuntimeId::new("llamacpp"), "an alias the runtime declares");
1091 assert_eq!(cat.named("vllm").unwrap_err(), "no runtime 'vllm' here");
1092 }
1093
1094 #[test]
1095 fn the_runtime_for_a_model_is_the_one_whose_format_it_is() {
1096 let (llamacpp, mlx) = (RuntimeId::new("llamacpp"), RuntimeId::new("mlx"));
1097 if crate::linked::get(llamacpp).is_some() || crate::linked::get(mlx).is_some() {
1098 return;
1099 }
1100 let home = scratch("formats");
1101 install_fake(&home, "llamacpp", &report("llamacpp", V, "true"), 0);
1102 let lib = home.join("runtimes/llamacpp/current/lib");
1103 std::fs::create_dir_all(&lib).unwrap();
1104 std::fs::write(lib.join(crate::dylib_tokenizer::library_file("superfluid_tokenizer_llamacpp")), b"").unwrap();
1105 install_fake(&home, "mlx", &report("mlx", V, "true"), 0);
1106 let cat = catalog(&home);
1107 let models = scratch("formats-models");
1108 let gguf = models.join("weights.bin");
1109 std::fs::write(&gguf, b"GGUF\x03\x00").unwrap();
1110 let dir = models.join("qwen-mlx");
1111 std::fs::create_dir_all(&dir).unwrap();
1112 std::fs::write(dir.join("config.json"), b"{}").unwrap();
1113 std::fs::write(dir.join("model.safetensors"), b"").unwrap();
1114 let notes = models.join("notes.txt");
1115 std::fs::write(¬es, b"hello").unwrap();
1116
1117 assert_eq!(cat.reader_of(&gguf).unwrap(), llamacpp, "by the magic its format declares");
1118 assert_eq!(cat.reader_of(&dir).unwrap(), mlx);
1119 let e = cat.reader_of(¬es).unwrap_err();
1120 assert!(e.starts_with(&format!("no runtime here reads {}", notes.display())), "{e}");
1121 assert!(e.contains("llamacpp reads a GGUF file; mlx reads an MLX model directory"), "{e}");
1122 assert!(e.contains("`superfluid runtime install <id>`"), "{e}");
1123
1124 let e = cat.reads(mlx, &gguf).unwrap_err();
1125 assert_eq!(
1126 e,
1127 format!(
1128 "runtime mlx cannot serve {}: it reads an MLX model directory, and this is a GGUF file (pass --runtime llamacpp, or convert the model)",
1129 gguf.display()
1130 )
1131 );
1132 let mut pick = crate::runtime_pick::RuntimePick::default();
1133 pick.add("vllm").unwrap();
1134 let e = pick.resolve(&gguf, &cat).unwrap_err();
1135 assert!(e.starts_with(&format!("no runtime 'vllm' here to serve {}", gguf.display())), "{e}");
1136 assert!(e.contains("it is a GGUF file, which llamacpp here reads: pass --runtime llamacpp"), "{e}");
1137 assert!(e.ends_with("`superfluid runtime install vllm` installs vllm"), "{e}");
1138 assert_eq!(crate::runtime_pick::RuntimePick::default().resolve(&dir, &cat).unwrap(), mlx);
1139 let mut pick = crate::runtime_pick::RuntimePick::default();
1140 pick.add("llama.cpp").unwrap();
1141 assert_eq!(pick.resolve(&gguf, &cat).unwrap(), llamacpp);
1142 }
1143}