Skip to content

Commit 23e19f4

Browse files
committed
Refactor a2ui_agent to depend on a2ui_core
1 parent ca5429c commit 23e19f4

15 files changed

Lines changed: 527 additions & 2183 deletions

File tree

agent_sdks/python/a2ui_agent/pyproject.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,22 @@ name = "a2ui-agent-sdk"
1717
dynamic = ["version"]
1818
description = "A2UI Agent SDK"
1919
readme = "README.md"
20-
requires-python = ">=3.10"
20+
requires-python = ">=3.14"
2121
dependencies = [
2222
"a2a-sdk>=0.3.0",
23+
"a2ui-core>=0.1.0",
2324
"google-adk>=1.28.1",
2425
"google-genai>=1.27.0",
2526
"jsonschema>=4.0.0"
2627
]
2728

29+
[project.scripts]
30+
pyink = "pyink:patched_main"
31+
32+
[tool.uv.sources]
33+
34+
a2ui-core = { path = "../a2ui_core", editable = true }
35+
2836
[build-system]
2937
requires = ["hatchling", "jsonschema"]
3038
build-backend = "hatchling.build"

agent_sdks/python/a2ui_agent/src/a2ui/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313

14+
from pkgutil import extend_path
15+
16+
__path__ = extend_path(__path__, __name__)
17+
1418
from .version import __version__

agent_sdks/python/a2ui_agent/src/a2ui/basic_catalog/provider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ..schema.utils import load_from_bundled_resource
2020
from ..schema.constants import BASE_SCHEMA_URL, CATALOG_ID_KEY, CATALOG_SCHEMA_KEY
2121
from .constants import BASIC_CATALOG_NAME, BASIC_CATALOG_PATHS
22+
from a2ui.core.catalog.json_catalog import JSON_SCHEMA_DRAFT_2020_12
2223

2324

2425
class BundledCatalogProvider(A2uiCatalogProvider):
@@ -43,7 +44,7 @@ def load(self) -> Dict[str, Any]:
4344
resource[CATALOG_ID_KEY] = BASE_SCHEMA_URL + catalog_file
4445

4546
if "$schema" not in resource:
46-
resource["$schema"] = "https://json-schema.org/draft/2020-12/schema"
47+
resource["$schema"] = JSON_SCHEMA_DRAFT_2020_12
4748

4849
return resource
4950

agent_sdks/python/a2ui_agent/src/a2ui/parser/streaming.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
extract_component_ref_fields,
3333
extract_component_required_fields,
3434
)
35+
from ..schema.validator import A2uiValidator
3536
from .response_part import ResponsePart
3637

3738

@@ -48,31 +49,27 @@ class A2uiStreamParser:
4849
(V08 or V09) depending on the catalog version.
4950
"""
5051

51-
def __new__(cls, catalog: "A2uiCatalog" = None, *args, **kwargs):
52+
def __new__(cls, catalog: A2uiCatalog):
5253
if cls is A2uiStreamParser:
53-
version = getattr(catalog, "version", None) if catalog else None
54-
if version == VERSION_0_9:
55-
from .streaming_v09 import A2uiStreamParserV09
54+
version = catalog.version
55+
# Lazy import inside __new__ to prevent circular import errors, as the
56+
# version-specific subclass modules import A2uiStreamParser from this module.
57+
if version == VERSION_0_8:
58+
from .streaming_v08 import A2uiStreamParserV08
5659

57-
return A2uiStreamParserV09(catalog=catalog, *args, **kwargs)
60+
return A2uiStreamParserV08(catalog=catalog)
5861
else:
59-
from .streaming_v08 import A2uiStreamParserV08
62+
from .streaming_v09 import A2uiStreamParserV09
6063

61-
return A2uiStreamParserV08(catalog=catalog, *args, **kwargs)
64+
return A2uiStreamParserV09(catalog=catalog)
6265
return super().__new__(cls)
6366

64-
def __init__(self, catalog: "A2uiCatalog" = None):
65-
self._version = getattr(catalog, "version", None) if catalog else None
66-
self._cuttable_keys = (
67-
getattr(catalog, "cuttable_keys", None) if catalog else frozenset()
68-
)
69-
self._ref_fields_map = extract_component_ref_fields(catalog) if catalog else {}
70-
self._required_fields_map = (
71-
extract_component_required_fields(catalog) if catalog else {}
72-
)
73-
from ..schema.validator import A2uiValidator
74-
75-
self._validator = A2uiValidator(catalog) if catalog else None
67+
def __init__(self, catalog: A2uiCatalog):
68+
self._version = catalog.version
69+
self._cuttable_keys = catalog.cuttable_keys
70+
self._ref_fields_map = extract_component_ref_fields(catalog)
71+
self._required_fields_map = extract_component_required_fields(catalog)
72+
self._validator = A2uiValidator(catalog)
7673

7774
self._found_delimiter = False
7875
self._buffer = ""
@@ -878,6 +875,8 @@ def yield_reachable(
878875
self.root_id,
879876
components_to_analyze,
880877
self._ref_fields_map,
878+
single_ref_fields=set(),
879+
list_ref_fields=set(),
881880
raise_on_orphans=raise_on_orphans,
882881
)
883882

agent_sdks/python/a2ui_agent/src/a2ui/parser/streaming_v08.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@
1414

1515
import re
1616
import json
17-
from typing import Any, List, Dict, Optional, Set
17+
from typing import Any, List, Dict, Optional, Set, TYPE_CHECKING
1818

1919
from .streaming import A2uiStreamParser
2020
from .response_part import ResponsePart
2121
from .constants import *
2222
from ..schema.constants import VERSION_0_8, SURFACE_ID_KEY, CATALOG_COMPONENTS_KEY
2323

24+
if TYPE_CHECKING:
25+
from ..schema.catalog import A2uiCatalog
26+
2427

2528
class A2uiStreamParserV08(A2uiStreamParser):
2629
"""Streaming parser implementation for A2UI v0.8 specification."""
2730

28-
def __init__(self, catalog=None):
31+
def __init__(self, catalog: A2uiCatalog):
2932
super().__init__(catalog=catalog)
3033
self._yielded_begin_rendering_surfaces: Set[str] = set()
3134

agent_sdks/python/a2ui_agent/src/a2ui/parser/streaming_v09.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@
1414

1515
import re
1616
import json
17-
from typing import Any, List, Dict, Optional, Set
17+
from typing import Any, List, Dict, Optional, Set, TYPE_CHECKING
1818

1919
from .streaming import A2uiStreamParser
2020
from .response_part import ResponsePart
2121
from .constants import *
2222
from ..schema.constants import VERSION_0_9, SURFACE_ID_KEY, CATALOG_COMPONENTS_KEY
2323

24+
if TYPE_CHECKING:
25+
from ..schema.catalog import A2uiCatalog
26+
2427

2528
class A2uiStreamParserV09(A2uiStreamParser):
2629
"""Streaming parser implementation for A2UI v0.9 specification."""
2730

28-
def __init__(self, catalog=None):
31+
def __init__(self, catalog: A2uiCatalog):
2932
super().__init__(catalog=catalog)
3033
# v0.9 default root is "root"
3134
self._default_root_id = DEFAULT_ROOT_ID

agent_sdks/python/a2ui_agent/src/a2ui/schema/catalog.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from dataclasses import dataclass, field, replace
2222
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
2323
from urllib.parse import urlparse
24+
from a2ui.core.catalog import JsonCatalog
2425

2526
from .catalog_provider import A2uiCatalogProvider, FileSystemCatalogProvider
2627
from .constants import (
@@ -32,7 +33,9 @@
3233
VERSION_0_8,
3334
ENCODING,
3435
)
35-
from .validator import A2uiValidator
36+
37+
if TYPE_CHECKING:
38+
from .validator import A2uiValidator
3639

3740

3841
@dataclass
@@ -48,12 +51,16 @@ class CatalogConfig:
4851
provider: The provider to use to load the catalog schema.
4952
examples_path: The path or glob pattern to the examples.
5053
custom_cuttable_keys: The optional custom set of cuttable keys.
54+
custom_single_refs: The optional list of refs to be treated as single items.
55+
custom_list_refs: The optional list of refs to be treated as list items.
5156
"""
5257

5358
name: str
5459
provider: A2uiCatalogProvider
5560
examples_path: Optional[str] = None
5661
custom_cuttable_keys: Optional[frozenset[str]] = None
62+
custom_single_refs: Optional[List[str]] = None
63+
custom_list_refs: Optional[List[str]] = None
5764

5865
@classmethod
5966
def from_path(
@@ -62,6 +69,8 @@ def from_path(
6269
catalog_path: str,
6370
examples_path: Optional[str] = None,
6471
custom_cuttable_keys: Optional[frozenset[str]] = None,
72+
custom_single_refs: Optional[List[str]] = None,
73+
custom_list_refs: Optional[List[str]] = None,
6574
) -> "CatalogConfig":
6675
"""Returns a CatalogConfig that loads from a local path or 'file://' URI."""
6776
parsed = urlparse(catalog_path)
@@ -79,6 +88,10 @@ def from_path(
7988
}
8089
if custom_cuttable_keys is not None:
8190
kwargs["custom_cuttable_keys"] = custom_cuttable_keys
91+
if custom_single_refs is not None:
92+
kwargs["custom_single_refs"] = custom_single_refs
93+
if custom_list_refs is not None:
94+
kwargs["custom_list_refs"] = custom_list_refs
8295

8396
return cls(**kwargs)
8497

@@ -151,6 +164,8 @@ class A2uiCatalog:
151164
catalog_schema: The catalog schema.
152165
custom_cuttable_keys: The optional set of keys whose string values can be safely auto-closed
153166
(healed) if fragmented in the stream. If None, the default set is used.
167+
custom_single_refs: The optional list of refs to be treated as single items.
168+
custom_list_refs: The optional list of refs to be treated as list items.
154169
"""
155170

156171
version: str
@@ -159,6 +174,8 @@ class A2uiCatalog:
159174
common_types_schema: Dict[str, Any]
160175
catalog_schema: Dict[str, Any]
161176
custom_cuttable_keys: Optional[frozenset[str]] = None
177+
custom_single_refs: Optional[List[str]] = None
178+
custom_list_refs: Optional[List[str]] = None
162179

163180
@property
164181
def cuttable_keys(self) -> frozenset[str]:
@@ -173,9 +190,22 @@ def catalog_id(self) -> str:
173190
return self.catalog_schema[CATALOG_ID_KEY]
174191

175192
@property
176-
def validator(self) -> A2uiValidator:
193+
def validator(self) -> "A2uiValidator":
194+
from .validator import A2uiValidator
195+
177196
return A2uiValidator(self)
178197

198+
@property
199+
def core_catalog(self) -> JsonCatalog:
200+
return JsonCatalog(
201+
version=self.version,
202+
catalog_schema=self.catalog_schema,
203+
catalog_id=self.catalog_id,
204+
common_types_schema=self.common_types_schema,
205+
custom_single_refs=self.custom_single_refs,
206+
custom_list_refs=self.custom_list_refs,
207+
)
208+
179209
def _with_pruned_components(self, allowed_components: List[str]) -> A2uiCatalog:
180210
"""Returns a new catalog with only allowed components.
181211

agent_sdks/python/a2ui_agent/src/a2ui/schema/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def _load_schemas(
9595
s2c_schema=self._server_to_client_schema,
9696
common_types_schema=self._common_types_schema,
9797
custom_cuttable_keys=config.custom_cuttable_keys,
98+
custom_single_refs=config.custom_single_refs,
99+
custom_list_refs=config.custom_list_refs,
98100
)
99101
self._supported_catalogs.append(catalog)
100102
self._catalog_example_paths[catalog.catalog_id] = config.examples_path

0 commit comments

Comments
 (0)