diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py b/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py index 35ddd46be8..c5a4d04cb3 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py @@ -360,9 +360,10 @@ def _extract_multimodal_source_fields( ) -> tuple[str | None, str | None, str | None, str | None]: """Extract ``(url, data, binary_id, mime_type)`` from an AG-UI multimodal part. - Handles both the current AG-UI spec (``source.value`` for base64 payloads) and the - legacy ``source.data`` field for backward compatibility. Returned values are the - raw extracted strings (or ``None`` when absent); callers apply their own defaults. + Handles both the current AG-UI spec (``source.value`` for both URL and base64 + payloads) and the legacy ``source.url``/``source.data`` fields for backward + compatibility. Returned values are the raw extracted strings (or ``None`` when + absent); callers apply their own defaults. """ mime_type = cast(str | None, part.get("mimeType") or part.get("mime_type")) url = cast(str | None, part.get("url") or part.get("uri")) @@ -378,7 +379,7 @@ def _extract_multimodal_source_fields( mime_type = source_mime if source_type in {"url", "uri"}: - url = cast(str | None, source_dict.get("url") or source_dict.get("uri")) + url = cast(str | None, source_dict.get("value") or source_dict.get("url") or source_dict.get("uri")) elif source_type in {"base64", "data", "binary"}: data = cast(str | None, source_dict.get("value") or source_dict.get("data")) elif source_type in {"id", "file"}: diff --git a/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py b/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py index d1f90157e6..c78bec3293 100644 --- a/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py +++ b/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py @@ -2045,3 +2045,39 @@ def test_parse_multimodal_media_part_unknown_source_value_fallback(): ) assert result is not None assert "aGVsbG8=" in result.uri # type: ignore[operator] # pyrefly: ignore[not-iterable] # ty: ignore[unsupported-operator] + + +def test_parse_multimodal_media_part_url_value_field(): + """Source with type='url' reads the URL from the 'value' field per AG-UI spec.""" + from agent_framework_ag_ui._message_adapters import _parse_multimodal_media_part + + result = _parse_multimodal_media_part( + { + "type": "document", + "source": { + "type": "url", + "value": "https://example.com/files/document.pdf", + "mime_type": "application/pdf", + }, + } + ) + assert result is not None + assert result.uri == "https://example.com/files/document.pdf" + assert result.media_type == "application/pdf" + + +def test_parse_multimodal_media_part_url_field_backward_compat(): + """Source with type='url' still supports the non-spec 'url' and 'uri' fields.""" + from agent_framework_ag_ui._message_adapters import _parse_multimodal_media_part + + from_url = _parse_multimodal_media_part( + {"type": "image", "source": {"type": "url", "url": "https://example.com/a.png"}} + ) + assert from_url is not None + assert from_url.uri == "https://example.com/a.png" + + from_uri = _parse_multimodal_media_part( + {"type": "image", "source": {"type": "uri", "uri": "https://example.com/b.png"}} + ) + assert from_uri is not None + assert from_uri.uri == "https://example.com/b.png"