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