fabro/lib/crates/fabro-model/src/model_ref.rs
Bryan Helmkamp 95fffac267 Flatten LanguageModel trait + ModelInfo into struct Model
Delete the single-implementor LanguageModel trait and merge its methods
into inherent impl on a renamed Model struct. Change provider field from
String to Provider enum, eliminating constant string↔enum conversions
across the codebase. Fix Provider serde attributes so OpenAi serializes
as "openai" (not "open_ai") to match catalog.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:48:55 -04:00

110 lines
3 KiB
Rust

use std::fmt;
use std::sync::Arc;
use crate::provider::Provider;
use crate::types::Model;
/// A reference to a model — either a fully resolved `Model` or a
/// provider + model-name pair that hasn't been looked up yet.
#[derive(Clone)]
pub enum ModelRef {
/// A model whose metadata has been resolved from the catalog.
Resolved(Arc<Model>),
/// An unresolved provider:model pair (e.g. from CLI input or config).
ByName { provider: Provider, model: String },
}
impl ModelRef {
/// The model identifier string (e.g. `"claude-opus-4-6"`).
#[must_use]
pub fn model_id(&self) -> &str {
match self {
Self::Resolved(m) => &m.id,
Self::ByName { model, .. } => model,
}
}
/// The provider for this model.
#[must_use]
pub fn provider(&self) -> Provider {
match self {
Self::Resolved(m) => m.provider,
Self::ByName { provider, .. } => *provider,
}
}
}
impl fmt::Display for ModelRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.provider(), self.model_id())
}
}
impl fmt::Debug for ModelRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Resolved(m) => write!(f, "ModelRef::Resolved({:?})", m.id),
Self::ByName { provider, model } => f
.debug_struct("ModelRef::ByName")
.field("provider", provider)
.field("model", model)
.finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn by_name_display() {
let r = ModelRef::ByName {
provider: Provider::Anthropic,
model: "claude-opus-4-6".to_string(),
};
assert_eq!(r.to_string(), "anthropic:claude-opus-4-6");
}
#[test]
fn by_name_accessors() {
let r = ModelRef::ByName {
provider: Provider::OpenAi,
model: "gpt-5.4".to_string(),
};
assert_eq!(r.model_id(), "gpt-5.4");
assert_eq!(r.provider(), Provider::OpenAi);
}
#[test]
fn resolved_display() {
let info = crate::catalog::Catalog::builtin()
.get("claude-opus-4-6")
.unwrap()
.clone();
let r = ModelRef::Resolved(Arc::new(info));
assert_eq!(r.to_string(), "anthropic:claude-opus-4-6");
}
#[test]
fn resolved_accessors() {
let info = crate::catalog::Catalog::builtin()
.get("gpt-5.4")
.unwrap()
.clone();
let r = ModelRef::Resolved(Arc::new(info));
assert_eq!(r.model_id(), "gpt-5.4");
assert_eq!(r.provider(), Provider::OpenAi);
}
#[test]
fn debug_format() {
let r = ModelRef::ByName {
provider: Provider::Gemini,
model: "gemini-3.1-pro-preview".to_string(),
};
let debug = format!("{r:?}");
assert!(debug.contains("ByName"));
assert!(debug.contains("Gemini"));
}
}