use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::services::{ApiSurface, GatewayConfig, ProviderRegistry};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeProfileResponse {
pub inference_gateway_base_url: String,
pub auth_scheme: String,
#[serde(default)]
pub models: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_model: Option<String>,
#[serde(default)]
pub organization_uuid: Option<String>,
#[serde(default)]
pub providers: Vec<ProviderHealth>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub model_limits: BTreeMap<String, AdvertisedLimits>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdvertisedLimits {
pub context_window: u32,
pub max_output_tokens: u32,
}
fn advertised_limits(
registry: &ProviderRegistry,
servable: impl Fn(&str) -> bool,
) -> BTreeMap<String, AdvertisedLimits> {
registry
.advertised_providers()
.flat_map(|entry| entry.models.iter())
.filter(|model| !model.hidden && model.limits.context_window > 0)
.filter(|model| servable(model.id.as_str()))
.map(|model| {
(
model.id.as_str().to_owned(),
AdvertisedLimits {
context_window: model.limits.context_window,
max_output_tokens: model.limits.max_output_tokens,
},
)
})
.collect()
}
pub const KNOWN_HOSTS: &[&str] = &[
"claude-code",
"claude-desktop",
"codex-cli",
"hermes",
"opencode",
];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderHealth {
pub name: String,
pub surface: ApiSurface,
pub configured: bool,
#[serde(default)]
pub models: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config_issue: Option<String>,
}
pub fn provider_health(
registry: &ProviderRegistry,
secret_present: impl Fn(&str) -> bool,
) -> Vec<ProviderHealth> {
registry
.advertised_providers()
.map(|entry| {
let secret = entry.api_key_secret.as_str();
let configured = secret_present(secret);
ProviderHealth {
name: entry.name.as_str().to_owned(),
surface: entry.surface,
configured,
models: entry
.models
.iter()
.filter(|model| !model.hidden)
.map(|model| model.id.as_str().to_owned())
.collect(),
config_issue: (!configured)
.then(|| format!("API key secret '{secret}' is not configured")),
}
})
.collect()
}
pub fn is_model_servable(
registry: &ProviderRegistry,
gateway: Option<&GatewayConfig>,
model: &str,
secret_present: &impl Fn(&str) -> bool,
) -> bool {
let configured = |provider: &str| {
registry
.find_provider(provider)
.is_some_and(|entry| secret_present(entry.api_key_secret.as_str()))
};
if let Some(gw) = gateway {
if let Some(route) = gw.find_route(model) {
return configured(route.provider.as_str())
|| route
.fallback_provider
.as_ref()
.is_some_and(|fallback| configured(fallback.as_str()));
}
if let Some(default) = gw.default_provider.as_ref() {
return configured(default.as_str());
}
}
registry
.providers
.iter()
.filter(|entry| entry.find_model(model).is_some())
.any(|entry| secret_present(entry.api_key_secret.as_str()))
}
#[derive(Debug, Clone)]
pub struct BridgeProfileParams<'a> {
pub inference_gateway_base_url: String,
pub auth_scheme: String,
pub organization_uuid: Option<String>,
pub default_model: Option<String>,
pub registry: &'a ProviderRegistry,
pub gateway: Option<&'a GatewayConfig>,
}
#[must_use]
pub fn build(
params: BridgeProfileParams<'_>,
secret_present: impl Fn(&str) -> bool,
) -> BridgeProfileResponse {
let BridgeProfileParams {
inference_gateway_base_url,
auth_scheme,
organization_uuid,
default_model,
registry,
gateway,
} = params;
let servable = |model: &str| is_model_servable(registry, gateway, model, &secret_present);
BridgeProfileResponse {
inference_gateway_base_url,
auth_scheme,
models: registry
.advertised_model_ids(&[])
.into_iter()
.filter(|model| servable(model))
.collect(),
default_model,
organization_uuid,
model_limits: advertised_limits(registry, servable),
providers: provider_health(registry, &secret_present),
}
}