Skip to main content

adk_ui/
protocol_capabilities.rs

1use serde::Serialize;
2
3/// Default runtime protocol profile for server integrations.
4pub const UI_DEFAULT_PROTOCOL: &str = "adk_ui";
5
6/// Tool envelope version used by protocol-aware legacy tool responses.
7pub const TOOL_ENVELOPE_VERSION: &str = "1.0";
8
9/// Supported runtime protocol profile values.
10pub const SUPPORTED_UI_PROTOCOLS: &[&str] = &["adk_ui", "a2ui", "ag_ui", "mcp_apps"];
11
12/// Planned deprecation metadata for runtime/profile consumers.
13#[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/// Implementation maturity for protocol support exposed to clients.
26#[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/// Upstream specification track referenced by the runtime capability signal.
36#[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/// Static capability contract for each supported UI protocol.
45#[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
152/// Normalize runtime UI profile aliases to canonical values.
153pub 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}