use async_trait::async_trait;
use everruns::{
AgentLoopError, ChatDriver, DiscoveredModel, DriverId, LlmCallConfig, LlmMessage,
LlmResponseStream, LlmStreamEvent, Provider, ProviderEndpoint, models,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let live = std::env::args().nth(1).is_some_and(|arg| arg == "--live");
let provider: Provider = if live {
#[cfg(feature = "openai")]
{
println!("OpenAI's own catalog, over HTTP.\n");
everruns::OpenAI::from_env()?.into()
}
#[cfg(not(feature = "openai"))]
{
return Err("Live mode requires: cargo run -p everruns --features openai --example model_catalog -- --live".into());
}
} else {
println!("Offline: a stub driver answers the catalog call.\n");
Provider::new("my-gateway", StubCatalog).with_driver_id(DriverId::OpenAI)
};
let catalog = match models::list(provider).await {
Ok(catalog) => catalog,
Err(models::CatalogError::NoCatalog) => {
println!("This provider offers no catalog; keep your curated list.");
return Ok(());
}
Err(error) => return Err(error.into()),
};
for model in catalog.iter().take(10) {
let name = model.display_name().unwrap_or(model.id());
print!("{:<28} {name}", model.id());
if let Some(window) = model.context_window() {
print!(" ({window} tokens)");
}
if model.supports_reasoning() {
print!(" [reasoning]");
}
println!();
}
let Some(picked) = catalog.into_iter().find(|model| model.supports_tools()) else {
println!("\nNo tool-calling model in this catalog.");
return Ok(());
};
println!("\npicked {}", picked.id());
if let Some(profile) = picked.profile()
&& let Some(cost) = profile.cost.as_ref()
{
println!(
"input ${:.2} / output ${:.2} per million tokens",
cost.input, cost.output
);
}
if live {
let answer = picked
.model()
.complete("Name the three primary colors.")
.await?;
println!("\n{answer}");
}
Ok(())
}
#[derive(Clone)]
struct StubCatalog;
#[async_trait]
impl ChatDriver for StubCatalog {
async fn chat_completion_stream(
&self,
_endpoint: &ProviderEndpoint,
_messages: Vec<LlmMessage>,
_config: &LlmCallConfig,
) -> Result<LlmResponseStream, AgentLoopError> {
Ok(Box::pin(futures::stream::iter([
Ok(LlmStreamEvent::TextDelta("red, yellow, blue".to_string())),
Ok(LlmStreamEvent::Done(Box::default())),
])))
}
async fn list_models(
&self,
_endpoint: &ProviderEndpoint,
) -> Result<Option<Vec<DiscoveredModel>>, AgentLoopError> {
Ok(Some(
["gpt-5.6-terra", "gpt-5-mini", "house-model-v1"]
.into_iter()
.map(|model_id| DiscoveredModel {
model_id: model_id.to_string(),
display_name: None,
created_at: None,
owned_by: None,
capabilities: vec!["chat".to_string()],
discovered_profile: None,
})
.collect(),
))
}
}