1use serde::Serialize;
2
3pub const UI_DEFAULT_PROTOCOL: &str = "adk_ui";
5
6pub const TOOL_ENVELOPE_VERSION: &str = "1.0";
8
9pub const SUPPORTED_UI_PROTOCOLS: &[&str] = &["adk_ui", "a2ui", "ag_ui", "mcp_apps"];
11
12#[derive(Debug, Clone, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct UiProtocolDeprecationSpec {
16 pub stage: &'static str,
17 pub announced_on: &'static str,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub sunset_target_on: Option<&'static str>,
20 pub replacement_protocols: &'static [&'static str],
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub note: Option<&'static str>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
27#[serde(rename_all = "snake_case")]
28pub enum UiProtocolImplementationTier {
29 Legacy,
30 NativeSubset,
31 HybridSubset,
32 CompatibilitySubset,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "snake_case")]
38pub enum UiProtocolSpecTrack {
39 Internal,
40 Stable,
41 Draft,
42}
43
44#[derive(Debug, Clone, Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct UiProtocolCapabilitySpec {
48 pub protocol: &'static str,
49 pub versions: &'static [&'static str],
50 pub implementation_tier: UiProtocolImplementationTier,
51 pub spec_track: UiProtocolSpecTrack,
52 pub summary: &'static str,
53 pub features: &'static [&'static str],
54 pub limitations: &'static [&'static str],
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub deprecation: Option<&'static UiProtocolDeprecationSpec>,
57}
58
59pub const ADK_UI_LEGACY_DEPRECATION: UiProtocolDeprecationSpec = UiProtocolDeprecationSpec {
60 stage: "planned",
61 announced_on: "2026-02-07",
62 sunset_target_on: Some("2026-12-31"),
63 replacement_protocols: &["a2ui", "ag_ui", "mcp_apps"],
64 note: Some("Legacy adk_ui profile remains supported during migration."),
65};
66
67pub const UI_PROTOCOL_CAPABILITIES: &[UiProtocolCapabilitySpec] = &[
68 UiProtocolCapabilitySpec {
69 protocol: "adk_ui",
70 versions: &["1.0"],
71 implementation_tier: UiProtocolImplementationTier::Legacy,
72 spec_track: UiProtocolSpecTrack::Internal,
73 summary: "Legacy internal runtime profile retained for backward compatibility during migration.",
74 features: &["legacy_components", "theme", "events"],
75 limitations: &[
76 "Deprecated for new integrations.",
77 "Does not represent a standard external protocol surface.",
78 ],
79 deprecation: Some(&ADK_UI_LEGACY_DEPRECATION),
80 },
81 UiProtocolCapabilitySpec {
82 protocol: "a2ui",
83 versions: &["0.9"],
84 implementation_tier: UiProtocolImplementationTier::HybridSubset,
85 spec_track: UiProtocolSpecTrack::Draft,
86 summary: "Core A2UI surface transport with flat-component alignment, metadata-aware client envelopes, and validation-capable renderer behavior.",
87 features: &[
88 "jsonl",
89 "flat_components",
90 "createSurface",
91 "updateComponents",
92 "updateDataModel",
93 "client_metadata",
94 "validation_feedback",
95 "basic_catalog_functions",
96 "local_actions",
97 ],
98 limitations: &[
99 "Treat current support as a v0.9-aligned subset while the upstream v0.9 spec remains draft.",
100 "The package now covers the practical metadata, validation, and basic catalog flows used by this repo, but it still does not claim every draft-only renderer/runtime feature.",
101 ],
102 deprecation: None,
103 },
104 UiProtocolCapabilitySpec {
105 protocol: "ag_ui",
106 versions: &["0.1"],
107 implementation_tier: UiProtocolImplementationTier::CompatibilitySubset,
108 spec_track: UiProtocolSpecTrack::Stable,
109 summary: "Compatibility-oriented AG-UI subset with native run-input ingestion, lifecycle events, custom surface transport, and stable text/tool event ingestion.",
110 features: &[
111 "event_stream",
112 "native_run_input_ingest",
113 "run_lifecycle",
114 "custom_surface_event",
115 "stable_text_events",
116 "stable_tool_events",
117 "messages_snapshot_ingest",
118 "run_error",
119 ],
120 limitations: &[
121 "The example client now prefers protocol-native AG-UI request input, but the bundled example server still serializes wrapped runtime events rather than a fully native AG-UI SSE envelope.",
122 "Activity snapshots/deltas, reasoning streams, and chunk aggregation are accepted as compatibility inputs but are not yet produced end-to-end by the example server.",
123 ],
124 deprecation: None,
125 },
126 UiProtocolCapabilitySpec {
127 protocol: "mcp_apps",
128 versions: &["sep-1865"],
129 implementation_tier: UiProtocolImplementationTier::CompatibilitySubset,
130 spec_track: UiProtocolSpecTrack::Stable,
131 summary: "Compatibility-oriented MCP Apps subset with initialize requests, bridge-aware structured tool results, ui:// resources, and inline HTML fallback surfaces.",
132 features: &[
133 "ui_resource_uri",
134 "tool_meta",
135 "structured_content",
136 "initialize_request",
137 "initialize_bridge_endpoint",
138 "message_bridge_endpoint",
139 "update_model_context_bridge_endpoint",
140 "bridge_host_context",
141 "bridge_app_capabilities",
142 "inline_html_resource",
143 ],
144 limitations: &[
145 "The example host/client path now supports initialize, message, and model-context bridge endpoints plus bridge-aware response rendering, but the library still exposes MCP Apps primarily through compatibility adapters rather than a standalone embedded app bridge API.",
146 "Static HTML resource fallback remains part of the public MCP Apps surface while broader host negotiation, resource notifications, and fully app-native bridge semantics are still partial.",
147 ],
148 deprecation: None,
149 },
150];
151
152pub fn normalize_runtime_ui_protocol(raw: &str) -> Option<&'static str> {
154 match raw.trim().to_ascii_lowercase().as_str() {
155 "adk_ui" => Some("adk_ui"),
156 "a2ui" => Some("a2ui"),
157 "ag_ui" | "ag-ui" => Some("ag_ui"),
158 "mcp_apps" | "mcp-apps" => Some("mcp_apps"),
159 _ => None,
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn normalize_runtime_protocol_accepts_aliases() {
169 assert_eq!(normalize_runtime_ui_protocol("adk_ui"), Some("adk_ui"));
170 assert_eq!(normalize_runtime_ui_protocol("A2UI"), Some("a2ui"));
171 assert_eq!(normalize_runtime_ui_protocol("ag-ui"), Some("ag_ui"));
172 assert_eq!(normalize_runtime_ui_protocol("mcp-apps"), Some("mcp_apps"));
173 assert_eq!(normalize_runtime_ui_protocol("unknown"), None);
174 }
175
176 #[test]
177 fn capability_specs_cover_supported_protocols() {
178 let protocols: Vec<&str> = UI_PROTOCOL_CAPABILITIES
179 .iter()
180 .map(|spec| spec.protocol)
181 .collect();
182 assert_eq!(protocols, SUPPORTED_UI_PROTOCOLS);
183 }
184
185 #[test]
186 fn capability_specs_include_versions() {
187 for spec in UI_PROTOCOL_CAPABILITIES {
188 assert!(
189 !spec.versions.is_empty(),
190 "missing versions for {}",
191 spec.protocol
192 );
193 assert!(
194 !spec.features.is_empty(),
195 "missing features for {}",
196 spec.protocol
197 );
198 assert!(
199 !spec.summary.trim().is_empty(),
200 "missing summary for {}",
201 spec.protocol
202 );
203 assert!(
204 !spec.limitations.is_empty(),
205 "missing limitations for {}",
206 spec.protocol
207 );
208 }
209 }
210
211 #[test]
212 fn legacy_profile_has_deprecation_metadata() {
213 let legacy = UI_PROTOCOL_CAPABILITIES
214 .iter()
215 .find(|spec| spec.protocol == "adk_ui")
216 .expect("adk_ui capability");
217 let deprecation = legacy.deprecation.expect("adk_ui deprecation metadata");
218 assert_eq!(deprecation.announced_on, "2026-02-07");
219 assert_eq!(deprecation.sunset_target_on, Some("2026-12-31"));
220 }
221
222 #[test]
223 fn capability_specs_capture_support_boundaries() {
224 let a2ui = UI_PROTOCOL_CAPABILITIES
225 .iter()
226 .find(|spec| spec.protocol == "a2ui")
227 .expect("a2ui capability");
228 assert_eq!(
229 a2ui.implementation_tier,
230 UiProtocolImplementationTier::HybridSubset
231 );
232 assert_eq!(a2ui.spec_track, UiProtocolSpecTrack::Draft);
233
234 let ag_ui = UI_PROTOCOL_CAPABILITIES
235 .iter()
236 .find(|spec| spec.protocol == "ag_ui")
237 .expect("ag_ui capability");
238 assert_eq!(
239 ag_ui.implementation_tier,
240 UiProtocolImplementationTier::CompatibilitySubset
241 );
242
243 let mcp_apps = UI_PROTOCOL_CAPABILITIES
244 .iter()
245 .find(|spec| spec.protocol == "mcp_apps")
246 .expect("mcp_apps capability");
247 assert_eq!(
248 mcp_apps.implementation_tier,
249 UiProtocolImplementationTier::CompatibilitySubset
250 );
251 assert_eq!(mcp_apps.spec_track, UiProtocolSpecTrack::Stable);
252 assert!(
253 mcp_apps.features.contains(&"initialize_request"),
254 "mcp_apps feature list should include initialize support"
255 );
256 }
257
258 #[test]
259 fn capability_specs_serialize_support_metadata_in_camel_case() {
260 let value = serde_json::to_value(UI_PROTOCOL_CAPABILITIES).expect("serialize capabilities");
261 let protocols = value.as_array().expect("capabilities array");
262 let a2ui = protocols
263 .iter()
264 .find(|spec| spec["protocol"] == "a2ui")
265 .expect("a2ui json capability");
266
267 assert_eq!(a2ui["implementationTier"], "hybrid_subset");
268 assert_eq!(a2ui["specTrack"], "draft");
269 assert!(a2ui["summary"].as_str().is_some());
270 assert!(
271 a2ui["limitations"]
272 .as_array()
273 .is_some_and(|limitations| !limitations.is_empty())
274 );
275 }
276}