1use serde::Serialize;
2
3pub const UI_DEFAULT_PROTOCOL: &str = "adk_ui";
9
10pub const UI_PREFERRED_PROTOCOL: &str = "a2ui";
12
13pub const TOOL_ENVELOPE_VERSION: &str = "1.0";
15
16pub const UI_CAPABILITY_SCHEMA_VERSION: &str = "2026-08-13";
18
19pub const SUPPORTED_UI_PROTOCOLS: &[&str] = &["adk_ui", "a2ui", "ag_ui", "mcp_apps"];
21
22#[derive(Debug, Clone, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct UiProtocolDeprecationSpec {
26 pub stage: &'static str,
27 pub announced_on: &'static str,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub sunset_target_on: Option<&'static str>,
30 pub replacement_protocols: &'static [&'static str],
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub note: Option<&'static str>,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "snake_case")]
38pub enum UiProtocolImplementationTier {
39 Legacy,
40 NativeSubset,
41 HybridSubset,
42 CompatibilitySubset,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
47#[serde(rename_all = "snake_case")]
48pub enum UiProtocolSpecTrack {
49 Internal,
50 Stable,
51 Draft,
52 Candidate,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
57#[serde(rename_all = "snake_case")]
58pub enum UiCapabilityStatus {
59 Supported,
60 Partial,
61 HostOwned,
62 Candidate,
63 Planned,
64 Legacy,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
69#[serde(rename_all = "snake_case")]
70pub enum UiCapabilityScope {
71 Runtime,
72 Client,
73 Transport,
74 Host,
75 Security,
76}
77
78#[derive(Debug, Clone, Serialize)]
80#[serde(rename_all = "camelCase")]
81pub struct UiProtocolVersionSpec {
82 pub version: &'static str,
83 pub spec_track: UiProtocolSpecTrack,
84 pub status: UiCapabilityStatus,
85 pub specification_url: &'static str,
86}
87
88#[derive(Debug, Clone, Serialize)]
90#[serde(rename_all = "camelCase")]
91pub struct UiProtocolNegotiationSpec {
92 pub mode: &'static str,
93 pub capability_keys: &'static [&'static str],
94 pub content_types: &'static [&'static str],
95 pub transports: &'static [&'static str],
96 pub fallback_protocols: &'static [&'static str],
97}
98
99#[derive(Debug, Clone, Serialize)]
102#[serde(rename_all = "camelCase")]
103pub struct UiProtocolCapabilityClaim {
104 pub id: &'static str,
105 pub status: UiCapabilityStatus,
106 pub scope: UiCapabilityScope,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub note: Option<&'static str>,
109}
110
111#[derive(Debug, Clone, Serialize)]
113#[serde(rename_all = "camelCase")]
114pub struct UiProtocolCapabilitySpec {
115 pub protocol: &'static str,
116 pub versions: &'static [&'static str],
117 pub implementation_tier: UiProtocolImplementationTier,
118 pub spec_track: UiProtocolSpecTrack,
119 pub reviewed_on: &'static str,
120 pub version_details: &'static [UiProtocolVersionSpec],
121 pub negotiation: UiProtocolNegotiationSpec,
122 pub capability_claims: &'static [UiProtocolCapabilityClaim],
123 pub summary: &'static str,
124 pub features: &'static [&'static str],
125 pub limitations: &'static [&'static str],
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub deprecation: Option<&'static UiProtocolDeprecationSpec>,
128}
129
130pub const ADK_UI_LEGACY_DEPRECATION: UiProtocolDeprecationSpec = UiProtocolDeprecationSpec {
131 stage: "planned",
132 announced_on: "2026-02-07",
133 sunset_target_on: Some("2026-12-31"),
134 replacement_protocols: &["a2ui", "ag_ui", "mcp_apps"],
135 note: Some(
136 "Legacy adk_ui profile remains supported during migration. Prefer a2ui for new integrations.",
137 ),
138};
139
140pub const UI_PROTOCOL_CAPABILITIES: &[UiProtocolCapabilitySpec] = &[
141 UiProtocolCapabilitySpec {
142 protocol: "adk_ui",
143 versions: &["1.0"],
144 implementation_tier: UiProtocolImplementationTier::Legacy,
145 spec_track: UiProtocolSpecTrack::Internal,
146 reviewed_on: UI_CAPABILITY_SCHEMA_VERSION,
147 version_details: &[UiProtocolVersionSpec {
148 version: "1.0",
149 spec_track: UiProtocolSpecTrack::Internal,
150 status: UiCapabilityStatus::Legacy,
151 specification_url: "https://github.com/zavora-ai/adk-ui",
152 }],
153 negotiation: UiProtocolNegotiationSpec {
154 mode: "profile_selection",
155 capability_keys: &["x-adk-ui-protocol", "uiProtocol"],
156 content_types: &["application/json", "text/event-stream"],
157 transports: &["http", "sse"],
158 fallback_protocols: &[],
159 },
160 capability_claims: &[
161 UiProtocolCapabilityClaim {
162 id: "legacy_surface_runtime",
163 status: UiCapabilityStatus::Legacy,
164 scope: UiCapabilityScope::Runtime,
165 note: Some("Retained only for compatibility through the published sunset window."),
166 },
167 UiProtocolCapabilityClaim {
168 id: "trusted_component_catalog",
169 status: UiCapabilityStatus::Supported,
170 scope: UiCapabilityScope::Security,
171 note: None,
172 },
173 ],
174 summary: "Legacy internal runtime profile retained for backward compatibility during migration.",
175 features: &["legacy_components", "theme", "events"],
176 limitations: &[
177 "Deprecated for new integrations.",
178 "Does not represent a standard external protocol surface.",
179 ],
180 deprecation: Some(&ADK_UI_LEGACY_DEPRECATION),
181 },
182 UiProtocolCapabilitySpec {
183 protocol: "a2ui",
184 versions: &["0.9.1", "1.0"],
185 implementation_tier: UiProtocolImplementationTier::NativeSubset,
186 spec_track: UiProtocolSpecTrack::Stable,
187 reviewed_on: UI_CAPABILITY_SCHEMA_VERSION,
188 version_details: &[
189 UiProtocolVersionSpec {
190 version: "0.9.1",
191 spec_track: UiProtocolSpecTrack::Stable,
192 status: UiCapabilityStatus::Supported,
193 specification_url: "https://a2ui.org/specification/v0.9.1-a2ui/",
194 },
195 UiProtocolVersionSpec {
196 version: "1.0",
197 spec_track: UiProtocolSpecTrack::Candidate,
198 status: UiCapabilityStatus::Candidate,
199 specification_url: "https://a2ui.org/specification/v1.0-a2ui/",
200 },
201 ],
202 negotiation: UiProtocolNegotiationSpec {
203 mode: "catalog_capability_exchange",
204 capability_keys: &[
205 "a2uiClientCapabilities.v0.9",
206 "a2uiRendererCapabilities.v1.0",
207 ],
208 content_types: &["application/a2ui+json", "application/json+a2ui"],
209 transports: &["jsonl", "sse", "a2a_host_binding"],
210 fallback_protocols: &["adk_ui"],
211 },
212 capability_claims: &[
213 UiProtocolCapabilityClaim {
214 id: "surface_lifecycle",
215 status: UiCapabilityStatus::Supported,
216 scope: UiCapabilityScope::Runtime,
217 note: Some("create, update components/data, and delete are supported."),
218 },
219 UiProtocolCapabilityClaim {
220 id: "catalog_negotiation",
221 status: UiCapabilityStatus::Partial,
222 scope: UiCapabilityScope::Transport,
223 note: Some(
224 "Versioned capability shapes are emitted; transport-specific selection remains host-owned.",
225 ),
226 },
227 UiProtocolCapabilityClaim {
228 id: "v1_correlated_rpc",
229 status: UiCapabilityStatus::Candidate,
230 scope: UiCapabilityScope::Client,
231 note: Some(
232 "actionResponse, callFunction, and functionResponse are implemented against the v1.0 release candidate.",
233 ),
234 },
235 UiProtocolCapabilityClaim {
236 id: "declarative_security",
237 status: UiCapabilityStatus::Supported,
238 scope: UiCapabilityScope::Security,
239 note: Some(
240 "Generated payloads are constrained to trusted component and function catalogs.",
241 ),
242 },
243 UiProtocolCapabilityClaim {
244 id: "a2a_transport_binding",
245 status: UiCapabilityStatus::HostOwned,
246 scope: UiCapabilityScope::Transport,
247 note: None,
248 },
249 ],
250 summary: "A2UI v0.9.1 production surfaces plus an explicitly candidate v1.0 RPC subset, with versioned catalog capability exchange and declarative rendering.",
251 features: &[
252 "jsonl",
253 "versioned_envelopes_v0_9_1",
254 "versioned_envelopes_v1_0",
255 "flat_components",
256 "createSurface",
257 "updateComponents",
258 "updateDataModel",
259 "deleteSurface",
260 "client_action_messages",
261 "action_want_response",
262 "action_response",
263 "call_function",
264 "function_response",
265 "validation_failed_errors",
266 "client_metadata",
267 "validation_feedback",
268 "basic_catalog_functions",
269 "local_actions",
270 "mime_application_a2ui_json",
271 "optional_bulk_createSurface_fields",
272 ],
273 limitations: &[
274 "A2UI v1.0 multi-catalog mix and full catalog composition constraints are partial; actionResponse/callFunction are implemented end-to-end.",
275 "Dedicated A2A transport bindings remain host-owned; this crate emits protocol JSONL and client action envelopes.",
276 ],
277 deprecation: None,
278 },
279 UiProtocolCapabilitySpec {
280 protocol: "ag_ui",
281 versions: &["0.1"],
282 implementation_tier: UiProtocolImplementationTier::NativeSubset,
283 spec_track: UiProtocolSpecTrack::Stable,
284 reviewed_on: UI_CAPABILITY_SCHEMA_VERSION,
285 version_details: &[UiProtocolVersionSpec {
286 version: "0.1",
287 spec_track: UiProtocolSpecTrack::Stable,
288 status: UiCapabilityStatus::Supported,
289 specification_url: "https://docs.ag-ui.com/",
290 }],
291 negotiation: UiProtocolNegotiationSpec {
292 mode: "dynamic_discovery",
293 capability_keys: &["getCapabilities", "agentCapabilities"],
294 content_types: &["text/event-stream", "application/json"],
295 transports: &["sse", "http"],
296 fallback_protocols: &["adk_ui"],
297 },
298 capability_claims: &[
299 UiProtocolCapabilityClaim {
300 id: "typed_agent_capabilities",
301 status: UiCapabilityStatus::Partial,
302 scope: UiCapabilityScope::Runtime,
303 note: Some(
304 "The example emits a truthful server baseline; loaded-agent tool, modality, sub-agent, and limit derivation remains framework-owned.",
305 ),
306 },
307 UiProtocolCapabilityClaim {
308 id: "event_stream",
309 status: UiCapabilityStatus::Supported,
310 scope: UiCapabilityScope::Transport,
311 note: None,
312 },
313 UiProtocolCapabilityClaim {
314 id: "state_and_activity_sync",
315 status: UiCapabilityStatus::Supported,
316 scope: UiCapabilityScope::Client,
317 note: None,
318 },
319 UiProtocolCapabilityClaim {
320 id: "encrypted_reasoning_continuity",
321 status: UiCapabilityStatus::Supported,
322 scope: UiCapabilityScope::Client,
323 note: Some(
324 "Opaque REASONING_ENCRYPTED_VALUE events are preserved without exposing chain-of-thought.",
325 ),
326 },
327 UiProtocolCapabilityClaim {
328 id: "interrupt_resume",
329 status: UiCapabilityStatus::Partial,
330 scope: UiCapabilityScope::Runtime,
331 note: Some(
332 "Wire types and resume ingestion are supported; durable pause/checkpoint policy is agent-owned.",
333 ),
334 },
335 UiProtocolCapabilityClaim {
336 id: "resumable_stream_sequence",
337 status: UiCapabilityStatus::Planned,
338 scope: UiCapabilityScope::Transport,
339 note: None,
340 },
341 ],
342 summary: "AG-UI event streaming with a typed capability model, state/activity synchronization, encrypted reasoning continuity, and interrupt-aware wire contracts.",
343 features: &[
344 "event_stream",
345 "run_lifecycle",
346 "run_started_parent_run_id",
347 "native_run_input_ingest",
348 "stable_text_events",
349 "text_message_chunk",
350 "stable_tool_events",
351 "tool_call_chunk",
352 "tool_call_result",
353 "state_snapshot",
354 "state_delta",
355 "messages_snapshot",
356 "activity_snapshot",
357 "activity_delta",
358 "reasoning_events",
359 "reasoning_message_chunk",
360 "reasoning_encrypted_value",
361 "agent_capability_discovery",
362 "interrupt_resume_wire_types",
363 "custom_surface_event",
364 "push_notification_channel",
365 "surface_activity_snapshot",
366 "run_error",
367 "raw_events",
368 ],
369 limitations: &[
370 "Example hosts may still wrap some ADK runtime events; prefer protocol_native SSE when available from adk-server.",
371 "Durable checkpointing and approval policy for interrupt/resume remain application-defined.",
372 "WebSocket, binary HTTP, and resumable sequence transports are not implemented by the example server; hosts must bridge the agent notification channel to their chosen push transport.",
373 ],
374 deprecation: None,
375 },
376 UiProtocolCapabilitySpec {
377 protocol: "mcp_apps",
378 versions: &["2026-01-26", "2025-11-21"],
379 implementation_tier: UiProtocolImplementationTier::NativeSubset,
380 spec_track: UiProtocolSpecTrack::Stable,
381 reviewed_on: UI_CAPABILITY_SCHEMA_VERSION,
382 version_details: &[
383 UiProtocolVersionSpec {
384 version: "2026-01-26",
385 spec_track: UiProtocolSpecTrack::Stable,
386 status: UiCapabilityStatus::Supported,
387 specification_url: "https://apps.extensions.modelcontextprotocol.io/",
388 },
389 UiProtocolVersionSpec {
390 version: "2025-11-21",
391 spec_track: UiProtocolSpecTrack::Candidate,
392 status: UiCapabilityStatus::Legacy,
393 specification_url: "https://github.com/modelcontextprotocol/ext-apps",
394 },
395 ],
396 negotiation: UiProtocolNegotiationSpec {
397 mode: "mcp_extension_negotiation",
398 capability_keys: &["capabilities.extensions.io.modelcontextprotocol/ui.mimeTypes"],
399 content_types: &["text/html;profile=mcp-app"],
400 transports: &["mcp_jsonrpc", "postmessage", "sandbox_proxy"],
401 fallback_protocols: &["text", "structured_content"],
402 },
403 capability_claims: &[
404 UiProtocolCapabilityClaim {
405 id: "progressive_enhancement",
406 status: UiCapabilityStatus::Supported,
407 scope: UiCapabilityScope::Runtime,
408 note: Some(
409 "UI-enabled tools retain meaningful text and structured-content fallbacks.",
410 ),
411 },
412 UiProtocolCapabilityClaim {
413 id: "extension_mime_negotiation",
414 status: UiCapabilityStatus::Partial,
415 scope: UiCapabilityScope::Transport,
416 note: Some(
417 "Capability shapes are emitted; host registration policy remains integration-owned.",
418 ),
419 },
420 UiProtocolCapabilityClaim {
421 id: "app_lifecycle",
422 status: UiCapabilityStatus::Partial,
423 scope: UiCapabilityScope::Host,
424 note: Some(
425 "Initialize, initialized, message, and model-context flows are implemented; teardown/display-mode flows are typed but host-owned.",
426 ),
427 },
428 UiProtocolCapabilityClaim {
429 id: "sandbox_and_csp",
430 status: UiCapabilityStatus::HostOwned,
431 scope: UiCapabilityScope::Security,
432 note: Some(
433 "Resource requirements are emitted and validated; the embedding host must enforce iframe isolation and CSP.",
434 ),
435 },
436 UiProtocolCapabilityClaim {
437 id: "tool_lifecycle_notifications",
438 status: UiCapabilityStatus::Partial,
439 scope: UiCapabilityScope::Client,
440 note: Some(
441 "Tool input/result shapes are supported; complete postMessage orchestration remains host-owned.",
442 ),
443 },
444 UiProtocolCapabilityClaim {
445 id: "host_mediated_downloads_and_links",
446 status: UiCapabilityStatus::HostOwned,
447 scope: UiCapabilityScope::Host,
448 note: None,
449 },
450 ],
451 summary: "MCP Apps stable 2026-01-26 resource, negotiation, sandbox metadata, progressive fallback, and interactive app lifecycle subset.",
452 features: &[
453 "extension_id_io_modelcontextprotocol_ui",
454 "ui_resource_uri",
455 "tool_meta_ui_resource_uri",
456 "html_profile_mcp_app",
457 "structured_content",
458 "initialize_request",
459 "initialize_bridge_endpoint",
460 "message_bridge_endpoint",
461 "update_model_context_bridge_endpoint",
462 "bridge_host_context",
463 "bridge_app_capabilities",
464 "extension_capability_advertisement",
465 "progressive_enhancement_fallback",
466 "tool_lifecycle_notifications",
467 "host_context_changed",
468 "resource_teardown",
469 "resource_list_changed_capability",
470 "tool_list_changed_capability",
471 "inline_html_resource",
472 "sandbox_csp_meta",
473 ],
474 limitations: &[
475 "Full browser postMessage host SDK is host-owned; adk-ui provides payloads + HTTP bridge shapes used with adk-server.",
476 "Bridge session durability depends on the host (adk-server in-memory by default).",
477 ],
478 deprecation: None,
479 },
480];
481
482#[cfg(feature = "awp")]
484pub const AWP_PROTOCOL_CAPABILITY: UiProtocolCapabilitySpec = UiProtocolCapabilitySpec {
485 protocol: "awp",
486 versions: &["1.0"],
487 implementation_tier: UiProtocolImplementationTier::CompatibilitySubset,
488 spec_track: UiProtocolSpecTrack::Draft,
489 reviewed_on: UI_CAPABILITY_SCHEMA_VERSION,
490 version_details: &[UiProtocolVersionSpec {
491 version: "1.0",
492 spec_track: UiProtocolSpecTrack::Draft,
493 status: UiCapabilityStatus::Partial,
494 specification_url: "https://github.com/zavora-ai/adk-rust",
495 }],
496 negotiation: UiProtocolNegotiationSpec {
497 mode: "capability_manifest",
498 capability_keys: &["capabilities"],
499 content_types: &["text/html", "application/json"],
500 transports: &["http"],
501 fallback_protocols: &["adk_ui"],
502 },
503 capability_claims: &[
504 UiProtocolCapabilityClaim {
505 id: "capability_manifest",
506 status: UiCapabilityStatus::Supported,
507 scope: UiCapabilityScope::Runtime,
508 note: None,
509 },
510 UiProtocolCapabilityClaim {
511 id: "interactive_hydration",
512 status: UiCapabilityStatus::HostOwned,
513 scope: UiCapabilityScope::Client,
514 note: None,
515 },
516 ],
517 summary: "AWP-aligned HTML rendering with capability manifest export and bandwidth-adaptive output.",
518 features: &[
519 "html_rendering",
520 "capability_manifest_export",
521 "bandwidth_adaptive",
522 "tool_envelope_bridge",
523 ],
524 limitations: &[
525 "HTML renderer produces static markup; interactive behaviors require client-side hydration.",
526 "Chart components render as data-attribute placeholders, not visual charts.",
527 "Site-level AWP (discovery, trust, rate limits) lives in adk-awp; this crate supplies UI payload/HTML only.",
528 ],
529 deprecation: None,
530};
531
532pub fn normalize_runtime_ui_protocol(raw: &str) -> Option<&'static str> {
534 match raw.trim().to_ascii_lowercase().as_str() {
535 "adk_ui" => Some("adk_ui"),
536 "a2ui" => Some("a2ui"),
537 "ag_ui" | "ag-ui" => Some("ag_ui"),
538 "mcp_apps" | "mcp-apps" => Some("mcp_apps"),
539 #[cfg(feature = "awp")]
540 "awp" => Some("awp"),
541 _ => None,
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn normalize_runtime_protocol_accepts_aliases() {
551 assert_eq!(normalize_runtime_ui_protocol("adk_ui"), Some("adk_ui"));
552 assert_eq!(normalize_runtime_ui_protocol("A2UI"), Some("a2ui"));
553 assert_eq!(normalize_runtime_ui_protocol("ag-ui"), Some("ag_ui"));
554 assert_eq!(normalize_runtime_ui_protocol("mcp-apps"), Some("mcp_apps"));
555 assert_eq!(normalize_runtime_ui_protocol("unknown"), None);
556 }
557
558 #[test]
559 fn capability_specs_cover_supported_protocols() {
560 let protocols: Vec<&str> = UI_PROTOCOL_CAPABILITIES
561 .iter()
562 .map(|spec| spec.protocol)
563 .collect();
564 assert_eq!(protocols, SUPPORTED_UI_PROTOCOLS);
565 }
566
567 #[test]
568 fn capability_specs_include_versions() {
569 for spec in UI_PROTOCOL_CAPABILITIES {
570 assert!(
571 !spec.versions.is_empty(),
572 "missing versions for {}",
573 spec.protocol
574 );
575 assert!(
576 !spec.features.is_empty(),
577 "missing features for {}",
578 spec.protocol
579 );
580 assert!(
581 !spec.summary.trim().is_empty(),
582 "missing summary for {}",
583 spec.protocol
584 );
585 assert!(
586 !spec.limitations.is_empty(),
587 "missing limitations for {}",
588 spec.protocol
589 );
590 assert_eq!(spec.reviewed_on, UI_CAPABILITY_SCHEMA_VERSION);
591 assert!(
592 !spec.version_details.is_empty(),
593 "missing version details for {}",
594 spec.protocol
595 );
596 assert!(
597 !spec.capability_claims.is_empty(),
598 "missing scoped claims for {}",
599 spec.protocol
600 );
601 assert!(
602 !spec.negotiation.transports.is_empty(),
603 "missing transports for {}",
604 spec.protocol
605 );
606 }
607 }
608
609 #[test]
610 fn legacy_profile_has_deprecation_metadata() {
611 let legacy = UI_PROTOCOL_CAPABILITIES
612 .iter()
613 .find(|spec| spec.protocol == "adk_ui")
614 .expect("adk_ui capability");
615 let deprecation = legacy.deprecation.expect("adk_ui deprecation metadata");
616 assert_eq!(deprecation.announced_on, "2026-02-07");
617 assert_eq!(deprecation.sunset_target_on, Some("2026-12-31"));
618 }
619
620 #[test]
621 fn modern_protocols_are_native_subset() {
622 for name in ["a2ui", "ag_ui", "mcp_apps"] {
623 let spec = UI_PROTOCOL_CAPABILITIES
624 .iter()
625 .find(|s| s.protocol == name)
626 .unwrap_or_else(|| panic!("missing {name}"));
627 assert_eq!(
628 spec.implementation_tier,
629 UiProtocolImplementationTier::NativeSubset,
630 "{name}"
631 );
632 assert_eq!(spec.spec_track, UiProtocolSpecTrack::Stable, "{name}");
633 }
634 let a2ui = UI_PROTOCOL_CAPABILITIES
635 .iter()
636 .find(|s| s.protocol == "a2ui")
637 .unwrap();
638 assert!(a2ui.versions.contains(&"0.9.1"));
639 assert!(a2ui.version_details.iter().any(|version| {
640 version.version == "1.0"
641 && version.spec_track == UiProtocolSpecTrack::Candidate
642 && version.status == UiCapabilityStatus::Candidate
643 }));
644
645 let mcp_apps = UI_PROTOCOL_CAPABILITIES
646 .iter()
647 .find(|s| s.protocol == "mcp_apps")
648 .unwrap();
649 assert_eq!(mcp_apps.versions.first(), Some(&"2026-01-26"));
650
651 let ag_ui = UI_PROTOCOL_CAPABILITIES
652 .iter()
653 .find(|s| s.protocol == "ag_ui")
654 .unwrap();
655 assert!(ag_ui.capability_claims.iter().any(|claim| {
656 claim.id == "interrupt_resume" && claim.status == UiCapabilityStatus::Partial
657 }));
658 }
659
660 #[test]
661 fn preferred_protocol_is_a2ui() {
662 assert_eq!(UI_PREFERRED_PROTOCOL, "a2ui");
663 assert!(normalize_runtime_ui_protocol(UI_PREFERRED_PROTOCOL).is_some());
664 }
665}