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/// AWP protocol capability specification.
153///
154/// Exposed as a standalone constant so downstream crates (adk-server, adk-gateway)
155/// can conditionally include it in their runtime protocol arrays.
156#[cfg(feature = "awp")]
157pub const AWP_PROTOCOL_CAPABILITY: UiProtocolCapabilitySpec = UiProtocolCapabilitySpec {
158    protocol: "awp",
159    versions: &["1.0"],
160    implementation_tier: UiProtocolImplementationTier::CompatibilitySubset,
161    spec_track: UiProtocolSpecTrack::Draft,
162    summary: "AWP-aligned HTML rendering with capability manifest export and bandwidth-adaptive output.",
163    features: &[
164        "html_rendering",
165        "capability_manifest_export",
166        "bandwidth_adaptive",
167        "tool_envelope_bridge",
168    ],
169    limitations: &[
170        "HTML renderer produces static markup; interactive behaviors require client-side hydration.",
171        "Chart components render as data-attribute placeholders, not visual charts.",
172    ],
173    deprecation: None,
174};
175
176/// Normalize runtime UI profile aliases to canonical values.
177pub fn normalize_runtime_ui_protocol(raw: &str) -> Option<&'static str> {
178    match raw.trim().to_ascii_lowercase().as_str() {
179        "adk_ui" => Some("adk_ui"),
180        "a2ui" => Some("a2ui"),
181        "ag_ui" | "ag-ui" => Some("ag_ui"),
182        "mcp_apps" | "mcp-apps" => Some("mcp_apps"),
183        #[cfg(feature = "awp")]
184        "awp" => Some("awp"),
185        _ => None,
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn normalize_runtime_protocol_accepts_aliases() {
195        assert_eq!(normalize_runtime_ui_protocol("adk_ui"), Some("adk_ui"));
196        assert_eq!(normalize_runtime_ui_protocol("A2UI"), Some("a2ui"));
197        assert_eq!(normalize_runtime_ui_protocol("ag-ui"), Some("ag_ui"));
198        assert_eq!(normalize_runtime_ui_protocol("mcp-apps"), Some("mcp_apps"));
199        assert_eq!(normalize_runtime_ui_protocol("unknown"), None);
200    }
201
202    #[test]
203    fn capability_specs_cover_supported_protocols() {
204        let protocols: Vec<&str> = UI_PROTOCOL_CAPABILITIES
205            .iter()
206            .map(|spec| spec.protocol)
207            .collect();
208        assert_eq!(protocols, SUPPORTED_UI_PROTOCOLS);
209    }
210
211    #[test]
212    fn capability_specs_include_versions() {
213        for spec in UI_PROTOCOL_CAPABILITIES {
214            assert!(
215                !spec.versions.is_empty(),
216                "missing versions for {}",
217                spec.protocol
218            );
219            assert!(
220                !spec.features.is_empty(),
221                "missing features for {}",
222                spec.protocol
223            );
224            assert!(
225                !spec.summary.trim().is_empty(),
226                "missing summary for {}",
227                spec.protocol
228            );
229            assert!(
230                !spec.limitations.is_empty(),
231                "missing limitations for {}",
232                spec.protocol
233            );
234        }
235    }
236
237    #[test]
238    fn legacy_profile_has_deprecation_metadata() {
239        let legacy = UI_PROTOCOL_CAPABILITIES
240            .iter()
241            .find(|spec| spec.protocol == "adk_ui")
242            .expect("adk_ui capability");
243        let deprecation = legacy.deprecation.expect("adk_ui deprecation metadata");
244        assert_eq!(deprecation.announced_on, "2026-02-07");
245        assert_eq!(deprecation.sunset_target_on, Some("2026-12-31"));
246    }
247
248    #[test]
249    fn capability_specs_capture_support_boundaries() {
250        let a2ui = UI_PROTOCOL_CAPABILITIES
251            .iter()
252            .find(|spec| spec.protocol == "a2ui")
253            .expect("a2ui capability");
254        assert_eq!(
255            a2ui.implementation_tier,
256            UiProtocolImplementationTier::HybridSubset
257        );
258        assert_eq!(a2ui.spec_track, UiProtocolSpecTrack::Draft);
259
260        let ag_ui = UI_PROTOCOL_CAPABILITIES
261            .iter()
262            .find(|spec| spec.protocol == "ag_ui")
263            .expect("ag_ui capability");
264        assert_eq!(
265            ag_ui.implementation_tier,
266            UiProtocolImplementationTier::CompatibilitySubset
267        );
268
269        let mcp_apps = UI_PROTOCOL_CAPABILITIES
270            .iter()
271            .find(|spec| spec.protocol == "mcp_apps")
272            .expect("mcp_apps capability");
273        assert_eq!(
274            mcp_apps.implementation_tier,
275            UiProtocolImplementationTier::CompatibilitySubset
276        );
277        assert_eq!(mcp_apps.spec_track, UiProtocolSpecTrack::Stable);
278        assert!(
279            mcp_apps.features.contains(&"initialize_request"),
280            "mcp_apps feature list should include initialize support"
281        );
282    }
283
284    #[test]
285    fn capability_specs_serialize_support_metadata_in_camel_case() {
286        let value = serde_json::to_value(UI_PROTOCOL_CAPABILITIES).expect("serialize capabilities");
287        let protocols = value.as_array().expect("capabilities array");
288        let a2ui = protocols
289            .iter()
290            .find(|spec| spec["protocol"] == "a2ui")
291            .expect("a2ui json capability");
292
293        assert_eq!(a2ui["implementationTier"], "hybrid_subset");
294        assert_eq!(a2ui["specTrack"], "draft");
295        assert!(a2ui["summary"].as_str().is_some());
296        assert!(
297            a2ui["limitations"]
298                .as_array()
299                .is_some_and(|limitations| !limitations.is_empty())
300        );
301    }
302}