systemprompt_models/profile/providers/surface.rs
1//! Client-facing API surface for an upstream AI provider.
2//!
3//! [`ApiSurface`] names the *vendor API family* a provider's models are
4//! advertised under, independent of the [`WireProtocol`] the gateway speaks to
5//! reach it. A provider can speak the Anthropic wire yet not be the Anthropic
6//! vendor: `minimax` declares `wire: anthropic` (so the gateway reuses the
7//! Anthropic codec) and `surface: backend` (so its `MiniMax-M2` model is never
8//! advertised to any client API, only reached through an explicit gateway
9//! route). Keeping these two facts orthogonal is what stops a backend provider
10//! from masquerading as an Anthropic model in a client's catalog.
11//!
12//! Copyright (c) systemprompt.io — Business Source License 1.1.
13//! See <https://systemprompt.io> for licensing details.
14
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
18pub enum ApiSurface {
19 #[serde(rename = "anthropic")]
20 Anthropic,
21 #[serde(rename = "openai")]
22 OpenAi,
23 #[serde(rename = "gemini")]
24 Gemini,
25 #[serde(rename = "backend")]
26 Backend,
27}
28
29impl ApiSurface {
30 #[must_use]
31 pub const fn as_tag(self) -> &'static str {
32 match self {
33 Self::Anthropic => "anthropic",
34 Self::OpenAi => "openai",
35 Self::Gemini => "gemini",
36 Self::Backend => "backend",
37 }
38 }
39
40 /// `backend` parses here (it is a valid declared surface) but callers that
41 /// resolve a *client* selection must reject it: a backend provider is never
42 /// a front door.
43 #[must_use]
44 pub fn from_tag(tag: &str) -> Option<Self> {
45 match tag {
46 "anthropic" => Some(Self::Anthropic),
47 "openai" => Some(Self::OpenAi),
48 "gemini" => Some(Self::Gemini),
49 "backend" => Some(Self::Backend),
50 _ => None,
51 }
52 }
53
54 /// The single definition of the advertisement rule.
55 ///
56 /// Every host-facing catalog (`/v1/models`, `/v1/bridge/profile`, the admin
57 /// profile page) derives its exclusion of backend providers from here
58 /// rather than re-checking the variant per call site.
59 #[must_use]
60 pub const fn is_advertised(self) -> bool {
61 !matches!(self, Self::Backend)
62 }
63}
64
65impl std::fmt::Display for ApiSurface {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str(self.as_tag())
68 }
69}