1use serde::Serialize;
7
8pub const UI_DEFAULT_PROTOCOL: &str = "adk_ui";
10
11pub const TOOL_ENVELOPE_VERSION: &str = "1.0";
13
14pub const SUPPORTED_UI_PROTOCOLS: &[&str] = &["adk_ui", "a2ui", "ag_ui", "mcp_apps"];
16
17#[derive(Debug, Clone, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct UiProtocolDeprecationSpec {
21 pub stage: &'static str,
22 pub announced_on: &'static str,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub sunset_target_on: Option<&'static str>,
25 pub replacement_protocols: &'static [&'static str],
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub note: Option<&'static str>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
32#[serde(rename_all = "snake_case")]
33pub enum UiProtocolImplementationTier {
34 Legacy,
35 NativeSubset,
36 HybridSubset,
37 CompatibilitySubset,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
42#[serde(rename_all = "snake_case")]
43pub enum UiProtocolSpecTrack {
44 Internal,
45 Stable,
46 Draft,
47}
48
49#[derive(Debug, Clone, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct UiProtocolCapabilitySpec {
53 pub protocol: &'static str,
54 pub versions: &'static [&'static str],
55 pub implementation_tier: UiProtocolImplementationTier,
56 pub spec_track: UiProtocolSpecTrack,
57 pub summary: &'static str,
58 pub features: &'static [&'static str],
59 pub limitations: &'static [&'static str],
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub deprecation: Option<&'static UiProtocolDeprecationSpec>,
62}
63
64pub const ADK_UI_LEGACY_DEPRECATION: UiProtocolDeprecationSpec = UiProtocolDeprecationSpec {
65 stage: "planned",
66 announced_on: "2026-02-07",
67 sunset_target_on: Some("2026-12-31"),
68 replacement_protocols: &["a2ui", "ag_ui", "mcp_apps"],
69 note: Some("Legacy adk_ui profile remains supported during migration."),
70};
71
72pub const UI_PROTOCOL_CAPABILITIES: &[UiProtocolCapabilitySpec] = &[
73 UiProtocolCapabilitySpec {
74 protocol: "adk_ui",
75 versions: &["1.0"],
76 implementation_tier: UiProtocolImplementationTier::Legacy,
77 spec_track: UiProtocolSpecTrack::Internal,
78 summary: "Legacy internal runtime profile retained for backward compatibility during migration.",
79 features: &["legacy_components", "theme", "events"],
80 limitations: &[
81 "Deprecated for new integrations.",
82 "Does not represent a standard external protocol contract.",
83 ],
84 deprecation: Some(&ADK_UI_LEGACY_DEPRECATION),
85 },
86 UiProtocolCapabilitySpec {
87 protocol: "a2ui",
88 versions: &["0.9"],
89 implementation_tier: UiProtocolImplementationTier::HybridSubset,
90 spec_track: UiProtocolSpecTrack::Draft,
91 summary: "A2UI-aligned JSONL surface output through protocol-aware UI tools and tool envelopes.",
92 features: &["jsonl", "createSurface", "updateComponents", "updateDataModel"],
93 limitations: &[
94 "The runtime stream is still the generic ADK event envelope rather than a dedicated A2UI transport.",
95 "The advertised shape is aligned to the draft v0.9 specification and should be treated as draft-compatible support.",
96 ],
97 deprecation: None,
98 },
99 UiProtocolCapabilitySpec {
100 protocol: "ag_ui",
101 versions: &["0.1"],
102 implementation_tier: UiProtocolImplementationTier::HybridSubset,
103 spec_track: UiProtocolSpecTrack::Stable,
104 summary: "Hybrid AG-UI subset with additive protocol-native runtime transport, dual-path AG-UI run input support, and compatibility wrappers for existing SSE consumers.",
105 features: &[
106 "run_lifecycle",
107 "custom_events",
108 "event_stream",
109 "native_runtime_transport",
110 "native_run_input_subset",
111 "state_snapshot",
112 "messages_snapshot",
113 "stable_text_events",
114 "stable_tool_events",
115 ],
116 limitations: &[
117 "Protocol-native AG-UI transport is opt-in and currently scoped to `adk-server`; the default runtime stream remains the generic ADK wrapper for compatibility.",
118 "The framework translates generic ADK runtime events into the stable AG-UI subset at the server boundary, but it does not yet emit the full activity, reasoning, or agent-native AG-UI event families end-to-end from `adk-agent`.",
119 ],
120 deprecation: None,
121 },
122 UiProtocolCapabilitySpec {
123 protocol: "mcp_apps",
124 versions: &["sep-1865"],
125 implementation_tier: UiProtocolImplementationTier::CompatibilitySubset,
126 spec_track: UiProtocolSpecTrack::Stable,
127 summary: "Compatibility-oriented MCP Apps profile with a ui:// resource registry, additive initialize/message/model-context bridge endpoints, and HTML/resource adapters.",
128 features: &[
129 "ui_resource_uri",
130 "tool_meta",
131 "html_resource",
132 "initialize_bridge_endpoint",
133 "message_bridge_endpoint",
134 "update_model_context_bridge_endpoint",
135 "notification_poll_endpoint",
136 "jsonrpc_bridge_envelope",
137 "runtime_bridge_request_fields",
138 "initialized_notification",
139 "resource_list_changed_notification",
140 "tool_list_changed_notification",
141 ],
142 limitations: &[
143 "The framework now exposes additive HTTP helpers plus notification polling for initialize, message, update-model-context, initialized, and list-changed host flows, but it does not yet provide a full browser postMessage transport.",
144 "Current MCP Apps support is still adapter-driven, and bridge session state remains in-memory rather than durably coupled to the runtime/session lifecycle.",
145 ],
146 deprecation: None,
147 },
148];
149
150pub fn normalize_runtime_ui_protocol(raw: &str) -> Option<&'static str> {
152 match raw.trim().to_ascii_lowercase().as_str() {
153 "adk_ui" => Some("adk_ui"),
154 "a2ui" => Some("a2ui"),
155 "ag_ui" | "ag-ui" => Some("ag_ui"),
156 "mcp_apps" | "mcp-apps" => Some("mcp_apps"),
157 _ => None,
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn normalize_runtime_protocol_accepts_aliases() {
167 assert_eq!(normalize_runtime_ui_protocol("adk_ui"), Some("adk_ui"));
168 assert_eq!(normalize_runtime_ui_protocol("A2UI"), Some("a2ui"));
169 assert_eq!(normalize_runtime_ui_protocol("ag-ui"), Some("ag_ui"));
170 assert_eq!(normalize_runtime_ui_protocol("mcp-apps"), Some("mcp_apps"));
171 assert_eq!(normalize_runtime_ui_protocol("unknown"), None);
172 }
173
174 #[test]
175 fn capability_specs_cover_supported_protocols() {
176 let protocols: Vec<&str> =
177 UI_PROTOCOL_CAPABILITIES.iter().map(|spec| spec.protocol).collect();
178 assert_eq!(protocols, SUPPORTED_UI_PROTOCOLS);
179 }
180
181 #[test]
182 fn capability_specs_include_versions() {
183 for spec in UI_PROTOCOL_CAPABILITIES {
184 assert!(!spec.versions.is_empty(), "missing versions for {}", spec.protocol);
185 assert!(!spec.features.is_empty(), "missing features for {}", spec.protocol);
186 assert!(!spec.summary.trim().is_empty(), "missing summary for {}", spec.protocol);
187 assert!(!spec.limitations.is_empty(), "missing limitations for {}", spec.protocol);
188 }
189 }
190
191 #[test]
192 fn legacy_profile_has_deprecation_metadata() {
193 let legacy = UI_PROTOCOL_CAPABILITIES
194 .iter()
195 .find(|spec| spec.protocol == "adk_ui")
196 .expect("adk_ui capability");
197 let deprecation = legacy.deprecation.expect("adk_ui deprecation metadata");
198 assert_eq!(deprecation.announced_on, "2026-02-07");
199 assert_eq!(deprecation.sunset_target_on, Some("2026-12-31"));
200 }
201
202 #[test]
203 fn capability_specs_capture_support_boundaries() {
204 let a2ui = UI_PROTOCOL_CAPABILITIES
205 .iter()
206 .find(|spec| spec.protocol == "a2ui")
207 .expect("a2ui capability");
208 assert_eq!(a2ui.implementation_tier, UiProtocolImplementationTier::HybridSubset);
209 assert_eq!(a2ui.spec_track, UiProtocolSpecTrack::Draft);
210
211 let ag_ui = UI_PROTOCOL_CAPABILITIES
212 .iter()
213 .find(|spec| spec.protocol == "ag_ui")
214 .expect("ag_ui capability");
215 assert_eq!(ag_ui.implementation_tier, UiProtocolImplementationTier::HybridSubset);
216 assert_eq!(ag_ui.spec_track, UiProtocolSpecTrack::Stable);
217
218 let mcp_apps = UI_PROTOCOL_CAPABILITIES
219 .iter()
220 .find(|spec| spec.protocol == "mcp_apps")
221 .expect("mcp_apps capability");
222 assert_eq!(mcp_apps.implementation_tier, UiProtocolImplementationTier::CompatibilitySubset);
223 assert_eq!(mcp_apps.spec_track, UiProtocolSpecTrack::Stable);
224 }
225
226 #[test]
227 fn capability_specs_serialize_support_metadata_in_camel_case() {
228 let value = serde_json::to_value(UI_PROTOCOL_CAPABILITIES).expect("serialize capabilities");
229 let protocols = value.as_array().expect("capabilities array");
230 let ag_ui =
231 protocols.iter().find(|spec| spec["protocol"] == "ag_ui").expect("ag_ui capability");
232
233 assert_eq!(ag_ui["implementationTier"], "hybrid_subset");
234 assert_eq!(ag_ui["specTrack"], "stable");
235 assert!(ag_ui["summary"].as_str().is_some());
236 assert!(ag_ui["limitations"].as_array().is_some_and(|limitations| !limitations.is_empty()));
237 }
238}