systemprompt_loader/vertex_discovery/
classify.rs1use serde::Deserialize;
31use systemprompt_manifest::services::{VertexRateCard, VertexRateCardEntry};
32use systemprompt_wire::WireProtocol;
33
34use super::source::{DiscoveredModel, LaunchStage};
35
36const MAAS_SUFFIX: &str = "-maas";
37
38const THIRD_PARTY_OSS: &str = "THIRD_PARTY_OWNED_OSS";
39
40const GOOGLE_PUBLISHER: &str = "google";
41
42const GA: &str = "GA";
43
44#[derive(Debug, Clone, Default, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct PublisherModel {
55 pub name: String,
56
57 #[serde(default)]
58 pub version_id: String,
59
60 #[serde(default)]
61 pub launch_stage: String,
62
63 #[serde(default)]
64 pub supported_actions: Option<serde_json::Value>,
66
67 #[serde(default)]
68 pub open_source_category: Option<String>,
69}
70
71impl PublisherModel {
72 #[must_use]
73 pub fn publisher(&self) -> &str {
74 let mut parts = self.name.split('/');
75 if parts.next() == Some("publishers") {
76 parts.next().unwrap_or_default()
77 } else {
78 ""
79 }
80 }
81
82 #[must_use]
83 pub fn model_name(&self) -> &str {
84 self.name.rsplit('/').next().unwrap_or(&self.name)
85 }
86
87 #[must_use]
88 pub fn upstream(&self) -> String {
89 format!("{}/{}", self.publisher(), self.model_name())
90 }
91
92 #[must_use]
93 pub fn is_generally_available(&self) -> bool {
94 self.launch_stage == GA
95 }
96
97 #[must_use]
98 pub fn discovered(&self) -> DiscoveredModel {
99 DiscoveredModel {
100 upstream: self.upstream(),
101 launch_stage: if self.is_generally_available() {
102 LaunchStage::GenerallyAvailable
103 } else {
104 LaunchStage::Preview
105 },
106 serverless: is_serverless(self),
107 }
108 }
109
110 fn is_deployable_checkpoint(&self) -> bool {
111 self.supported_actions
112 .as_ref()
113 .is_some_and(|actions| actions.get("deploy").is_some())
114 }
115
116 fn declares_actions(&self) -> bool {
117 match self.supported_actions.as_ref() {
118 None | Some(serde_json::Value::Null) => false,
119 Some(serde_json::Value::Object(actions)) => !actions.is_empty(),
120 Some(_) => true,
121 }
122 }
123}
124
125#[must_use]
126pub fn is_serverless(model: &PublisherModel) -> bool {
127 if model.is_deployable_checkpoint() {
128 return false;
129 }
130 if model.publisher() == GOOGLE_PUBLISHER {
131 return true;
132 }
133 model.model_name().ends_with(MAAS_SUFFIX)
134 && model.open_source_category.as_deref() == Some(THIRD_PARTY_OSS)
135 && !model.declares_actions()
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum Classification {
145 NotServerless,
146 Unpriced,
147 PreviewWithheld,
148 Publish,
149}
150
151#[must_use]
152pub fn classify<'a>(
153 model: &PublisherModel,
154 card: &'a VertexRateCard,
155 wire: WireProtocol,
156) -> (Classification, Option<&'a VertexRateCardEntry>) {
157 classify_discovered(&model.discovered(), card, wire)
158}
159
160#[must_use]
161pub fn classify_discovered<'a>(
162 model: &DiscoveredModel,
163 card: &'a VertexRateCard,
164 wire: WireProtocol,
165) -> (Classification, Option<&'a VertexRateCardEntry>) {
166 if !model.serverless {
167 return (Classification::NotServerless, None);
168 }
169 let Some(entry) = card.lookup(&model.upstream).filter(|e| e.wire == wire) else {
170 return (Classification::Unpriced, None);
171 };
172 if !model.launch_stage.is_generally_available() && !entry.allow_preview {
173 return (Classification::PreviewWithheld, Some(entry));
174 }
175 (Classification::Publish, Some(entry))
176}