Skip to main content

systemprompt_models/bridge/
profile.rs

1//! Wire contract for `GET /v1/bridge/profile`.
2//!
3//! The desktop bridge (`bin/bridge`) fetches this to render host configuration
4//! and to decide which provider models each host advertises. The server
5//! (`crates/entry/api`) produces it and the bridge consumes it through these
6//! exact types, so the two sides cannot drift.
7//!
8//! Every field is derived in [`build`] from
9//! [`ProviderRegistry::advertised_providers`], the single bearer of the
10//! advertisement rule ([`ApiSurface::is_advertised`]). A `surface: backend`
11//! provider is therefore structurally absent from both `providers` and the
12//! flat `models` front door — the flat list is a projection of the same
13//! advertised set, so it can never disagree with `providers`.
14//!
15//! Copyright (c) systemprompt.io — Business Source License 1.1.
16//! See <https://systemprompt.io> for licensing details.
17
18use serde::{Deserialize, Serialize};
19
20use crate::profile::{ApiSurface, ProviderRegistry};
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct BridgeProfileResponse {
24    pub inference_gateway_base_url: String,
25    pub auth_scheme: String,
26    #[serde(default)]
27    pub models: Vec<String>,
28    #[serde(default)]
29    pub organization_uuid: Option<String>,
30    #[serde(default)]
31    pub providers: Vec<ProviderHealth>,
32}
33
34/// A provider whose credential secret is absent is flagged
35/// (`configured = false`) rather than dropped silently.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ProviderHealth {
38    pub name: String,
39    pub surface: ApiSurface,
40    pub configured: bool,
41    #[serde(default)]
42    pub models: Vec<String>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub config_issue: Option<String>,
45}
46
47pub fn provider_health(
48    registry: &ProviderRegistry,
49    secret_present: impl Fn(&str) -> bool,
50) -> Vec<ProviderHealth> {
51    registry
52        .advertised_providers()
53        .map(|entry| {
54            let secret = entry.api_key_secret.as_str();
55            let configured = secret_present(secret);
56            ProviderHealth {
57                name: entry.name.as_str().to_owned(),
58                surface: entry.surface,
59                configured,
60                models: entry
61                    .models
62                    .iter()
63                    .flat_map(|m| {
64                        std::iter::once(m.id.as_str().to_owned())
65                            .chain(m.aliases.iter().map(|a| a.as_str().to_owned()))
66                    })
67                    .collect(),
68                config_issue: (!configured)
69                    .then(|| format!("API key secret '{secret}' is not configured")),
70            }
71        })
72        .collect()
73}
74
75#[must_use]
76pub fn build(
77    inference_gateway_base_url: String,
78    auth_scheme: String,
79    organization_uuid: Option<String>,
80    registry: &ProviderRegistry,
81    secret_present: impl Fn(&str) -> bool,
82) -> BridgeProfileResponse {
83    BridgeProfileResponse {
84        inference_gateway_base_url,
85        auth_scheme,
86        models: registry.advertised_model_ids(&[ApiSurface::Anthropic]),
87        organization_uuid,
88        providers: provider_health(registry, secret_present),
89    }
90}