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