use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ModelId(pub String);
impl ModelId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for ModelId {
fn from(id: &str) -> Self {
ModelId(id.to_string())
}
}
impl From<String> for ModelId {
fn from(id: String) -> Self {
ModelId(id)
}
}
impl std::fmt::Display for ModelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Reach {
FirstPartyApi,
CloudPartner,
PrivateEndpoint,
LocalCli,
SelfHosted,
}
impl Reach {
pub const ALL: [Reach; 5] = [
Reach::FirstPartyApi,
Reach::CloudPartner,
Reach::PrivateEndpoint,
Reach::LocalCli,
Reach::SelfHosted,
];
pub fn is_on_device(self) -> bool {
matches!(self, Reach::SelfHosted)
}
pub fn uses_local_credential(self) -> bool {
matches!(self, Reach::LocalCli | Reach::SelfHosted)
}
pub fn as_str(self) -> &'static str {
match self {
Reach::FirstPartyApi => "first-party-api",
Reach::CloudPartner => "cloud-partner",
Reach::PrivateEndpoint => "private-endpoint",
Reach::LocalCli => "local-cli",
Reach::SelfHosted => "self-hosted",
}
}
pub fn parse(name: &str) -> Option<Reach> {
let name = name.trim().to_ascii_lowercase().replace('_', "-");
Some(match name.as_str() {
"first-party-api" | "api" => Reach::FirstPartyApi,
"cloud-partner" => Reach::CloudPartner,
"private-endpoint" => Reach::PrivateEndpoint,
"local-cli" | "cli" => Reach::LocalCli,
"self-hosted" => Reach::SelfHosted,
_ => return None,
})
}
}
impl std::fmt::Display for Reach {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ModelCapabilities {
pub context_window: u32,
pub max_output: u32,
pub tools: bool,
pub structured_output: bool,
pub prompt_caching: bool,
pub thinking: bool,
pub images: bool,
pub streaming: bool,
pub reach: Reach,
}
impl ModelCapabilities {
pub fn none(reach: Reach) -> Self {
Self {
context_window: 0,
max_output: 0,
tools: false,
structured_output: false,
prompt_caching: false,
thinking: false,
images: false,
streaming: false,
reach,
}
}
#[must_use]
pub fn with_window(mut self, context_window: u32, max_output: u32) -> Self {
self.context_window = context_window;
self.max_output = max_output;
self
}
#[must_use]
pub fn with_tools(mut self) -> Self {
self.tools = true;
self
}
#[must_use]
pub fn with_structured_output(mut self) -> Self {
self.structured_output = true;
self
}
#[must_use]
pub fn with_prompt_caching(mut self) -> Self {
self.prompt_caching = true;
self
}
#[must_use]
pub fn with_thinking(mut self) -> Self {
self.thinking = true;
self
}
#[must_use]
pub fn with_images(mut self) -> Self {
self.images = true;
self
}
#[must_use]
pub fn with_streaming(mut self) -> Self {
self.streaming = true;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_local_cli_keeps_the_key_and_still_sends_the_data_away() {
assert!(Reach::LocalCli.uses_local_credential());
assert!(!Reach::LocalCli.is_on_device());
}
#[test]
fn only_self_hosted_keeps_the_data() {
for reach in Reach::ALL {
assert_eq!(reach.is_on_device(), reach == Reach::SelfHosted);
}
}
#[test]
fn every_reach_reads_back_from_how_it_is_written() {
for reach in Reach::ALL {
assert_eq!(Reach::parse(reach.as_str()), Some(reach));
}
}
#[test]
fn configuration_may_spell_it_either_way() {
assert_eq!(Reach::parse("local_cli"), Some(Reach::LocalCli));
assert_eq!(Reach::parse(" Local-CLI "), Some(Reach::LocalCli));
assert_eq!(Reach::parse("cli"), Some(Reach::LocalCli));
}
#[test]
fn a_reach_this_crate_does_not_know_is_none_rather_than_a_default() {
assert_eq!(Reach::parse("carrier-pigeon"), None);
}
}