Skip to content

Commit b3cce34

Browse files
committed
Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com>
feat: upgrade to dir v1.5.0 and implement grpc services Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com>
1 parent 8ed7435 commit b3cce34

14 files changed

Lines changed: 406 additions & 65 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ body:
3030
If applicable, provide the version number or release tag where this
3131
issue was encountered
3232
options:
33+
- v1.5.0
3334
- v1.4.0
3435
- v1.3.0
3536
- v1.2.1

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
[agntcy/dir]: https://github.com/agntcy/dir
44
[agntcy/dir-sdk-python]: https://github.com/agntcy/dir-sdk-python
55

6+
## 1.5.0 (2026-06-18)
7+
8+
### Added
9+
10+
- `AIFinderService` client methods: `list_agents`, `get_agent`, `export_agent`, and `get_well_known_catalog`.
11+
- `catalog_v1` model re-exports for AI Catalog protobuf types.
12+
- Integration tests for AI Finder against catalog-projectable OASF records.
13+
14+
### Changed
15+
16+
- Updated buf-generated SDK dependencies to track [agntcy/dir][agntcy/dir] `v1.5.0`.
17+
- Bumped the directory chart and `dirctl` image used in CI to `v1.5.0`.
18+
619
## 1.4.0 (2026-06-12)
720

821
### Added

RELEASE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Create a branch for the new release:
1212
- `examples/pyproject.toml`
1313
- `.github/ISSUE_TEMPLATE/bug_report.yml`
1414
2. Update the dependencies if necessary:
15-
- [agntcy-dir-grpc-python](https://buf.build/agntcy/dir/sdks/v1.4.0%3Agrpc/python) (Buf SDK)
16-
- [agntcy-dir-protocolbuffers-python](https://buf.build/agntcy/dir/sdks/v1.4.0%3Aprotocolbuffers/python) (Buf SDK)
15+
- [agntcy-dir-grpc-python](https://buf.build/agntcy/dir/sdks/v1.5.0%3Agrpc/python) (Buf SDK)
16+
- [agntcy-dir-protocolbuffers-python](https://buf.build/agntcy/dir/sdks/v1.5.0%3Aprotocolbuffers/python) (Buf SDK)
1717
- `.github/workflows/ci.yaml` (dir & dir-ctl version)
1818
3. Add an entry to `CHANGELOG.md`
1919

@@ -30,6 +30,6 @@ To trigger the release workflow, create and push the release tag
3030
for the last commit:
3131

3232
```sh
33-
git tag -a v1.4.0
34-
git push origin v1.4.0
33+
git tag -a v1.5.0
34+
git push origin v1.5.0
3535
```

Taskfile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
version: "3"
55

66
vars:
7-
DIR_CHART_VERSION: '{{ .DIR_CHART_VERSION | default "v1.4.0" }}'
8-
DIRCTL_IMAGE_TAG: '{{ .DIRCTL_IMAGE_TAG | default "v1.4.0" }}'
7+
DIR_CHART_VERSION: '{{ .DIR_CHART_VERSION | default "v1.5.0" }}'
8+
DIRCTL_IMAGE_TAG: '{{ .DIRCTL_IMAGE_TAG | default "v1.5.0" }}'
99

1010
# renovate: datasource=github-releases depName=renovatebot/renovate versioning=semver
1111
RENOVATE_VERSION: "43.209.4"

dir-sdk-python/agntcy/dir_sdk/client/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from agntcy.dir_sdk.client.auth.session import OAuthSessionManager
1919
from agntcy.dir_sdk.client.auth.token_cache import CachedToken, TokenCache
2020
from agntcy.dir_sdk.client.config import Config
21+
from agntcy.dir_sdk.client.services.ai_finder import AIFinderService
2122
from agntcy.dir_sdk.client.services.events import EventService
2223
from agntcy.dir_sdk.client.services.naming import NamingService
2324
from agntcy.dir_sdk.client.services.publication import PublicationService
@@ -32,6 +33,7 @@
3233
JWTAuthInterceptor,
3334
)
3435
from agntcy.dir_sdk.models import (
36+
catalog_v1,
3537
core_v1,
3638
events_v1,
3739
naming_v1,
@@ -40,6 +42,7 @@
4042
sign_v1,
4143
store_v1,
4244
)
45+
from google.api.httpbody_pb2 import HttpBody
4346

4447
logger = logging.getLogger("client")
4548

@@ -76,6 +79,7 @@ def __init__(self, config: Config | None = None) -> None:
7679
self.sync_client = store_v1.SyncServiceStub(channel)
7780
self.event_client = events_v1.EventServiceStub(channel)
7881
self.naming_client = naming_v1.NamingServiceStub(channel)
82+
self.ai_finder_client = catalog_v1.AIFinderServiceStub(channel)
7983

8084
# Service-layer adapters grouped by technical area.
8185
self.store_service = StoreService(self.store_client, logger)
@@ -86,6 +90,7 @@ def __init__(self, config: Config | None = None) -> None:
8690
self.sync_service = SyncService(self.sync_client, logger)
8791
self.event_service = EventService(self.event_client, logger)
8892
self.naming_service = NamingService(self.naming_client, logger)
93+
self.ai_finder_service = AIFinderService(self.ai_finder_client, logger)
8994

9095
def has_cached_oauth_token(self) -> bool:
9196
return self.oauth_session.has_access_token()
@@ -278,5 +283,33 @@ def verify(
278283
) -> sign_v1.VerifyResponse:
279284
return self.sign_service.verify(req, metadata=metadata)
280285

286+
def list_agents(
287+
self,
288+
req: catalog_v1.ListAgentsRequest,
289+
metadata: Sequence[tuple[str, str]] | None = None,
290+
) -> catalog_v1.ListAgentsResponse:
291+
return self.ai_finder_service.list_agents(req, metadata=metadata)
292+
293+
def get_agent(
294+
self,
295+
req: catalog_v1.GetAgentRequest,
296+
metadata: Sequence[tuple[str, str]] | None = None,
297+
) -> catalog_v1.GetAgentResponse:
298+
return self.ai_finder_service.get_agent(req, metadata=metadata)
299+
300+
def export_agent(
301+
self,
302+
req: catalog_v1.ExportAgentRequest,
303+
metadata: Sequence[tuple[str, str]] | None = None,
304+
) -> HttpBody:
305+
return self.ai_finder_service.export_agent(req, metadata=metadata)
306+
307+
def get_well_known_catalog(
308+
self,
309+
req: catalog_v1.GetWellKnownCatalogRequest,
310+
metadata: Sequence[tuple[str, str]] | None = None,
311+
) -> catalog_v1.GetWellKnownCatalogResponse:
312+
return self.ai_finder_service.get_well_known_catalog(req, metadata=metadata)
313+
281314
def sign(self, req: sign_v1.SignRequest) -> None:
282315
self.sign_service.sign(req)

dir-sdk-python/agntcy/dir_sdk/client/services/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""Service-layer wrappers around generated gRPC stubs."""
55

6+
from agntcy.dir_sdk.client.services.ai_finder import AIFinderService
67
from agntcy.dir_sdk.client.services.events import EventService
78
from agntcy.dir_sdk.client.services.naming import NamingService
89
from agntcy.dir_sdk.client.services.publication import PublicationService
@@ -13,6 +14,7 @@
1314
from agntcy.dir_sdk.client.services.sync import SyncService
1415

1516
__all__ = [
17+
"AIFinderService",
1618
"EventService",
1719
"NamingService",
1820
"PublicationService",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""AI Finder service wrappers."""
5+
6+
from __future__ import annotations
7+
8+
import logging
9+
from collections.abc import Sequence
10+
11+
from agntcy.dir_sdk.client.services.base import RpcServiceBase
12+
from agntcy.dir_sdk.models import catalog_v1
13+
from google.api.httpbody_pb2 import HttpBody
14+
15+
16+
class AIFinderService(RpcServiceBase):
17+
def __init__(
18+
self,
19+
ai_finder_client: catalog_v1.AIFinderServiceStub,
20+
logger: logging.Logger,
21+
) -> None:
22+
super().__init__(logger)
23+
self._ai_finder_client = ai_finder_client
24+
25+
def list_agents(
26+
self,
27+
req: catalog_v1.ListAgentsRequest,
28+
metadata: Sequence[tuple[str, str]] | None = None,
29+
) -> catalog_v1.ListAgentsResponse:
30+
return self._invoke(
31+
"list_agents",
32+
"Failed to list agents",
33+
lambda: self._ai_finder_client.ListAgents(req, metadata=metadata),
34+
)
35+
36+
def get_agent(
37+
self,
38+
req: catalog_v1.GetAgentRequest,
39+
metadata: Sequence[tuple[str, str]] | None = None,
40+
) -> catalog_v1.GetAgentResponse:
41+
return self._invoke(
42+
"get_agent",
43+
"Failed to get agent",
44+
lambda: self._ai_finder_client.GetAgent(req, metadata=metadata),
45+
)
46+
47+
def export_agent(
48+
self,
49+
req: catalog_v1.ExportAgentRequest,
50+
metadata: Sequence[tuple[str, str]] | None = None,
51+
) -> HttpBody:
52+
return self._invoke(
53+
"export_agent",
54+
"Failed to export agent",
55+
lambda: self._ai_finder_client.ExportAgent(req, metadata=metadata),
56+
)
57+
58+
def get_well_known_catalog(
59+
self,
60+
req: catalog_v1.GetWellKnownCatalogRequest,
61+
metadata: Sequence[tuple[str, str]] | None = None,
62+
) -> catalog_v1.GetWellKnownCatalogResponse:
63+
return self._invoke(
64+
"get_well_known_catalog",
65+
"Failed to get well-known catalog",
66+
lambda: self._ai_finder_client.GetWellKnownCatalog(req, metadata=metadata),
67+
)

dir-sdk-python/agntcy/dir_sdk/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Export all protobuf packages for easier module imports.
55
# The actual subpackages in agntcy_dir.models expose gRPC stubs.
66

7+
from . import catalog_v1 as catalog_v1
78
from . import core_v1 as core_v1
89
from . import events_v1 as events_v1
910
from . import naming_v1 as naming_v1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from agntcy.dir.catalog.v1.ai_finder_service_pb2 import *
5+
from agntcy.dir.catalog.v1.ai_finder_service_pb2_grpc import *
6+
from agntcy.dir.catalog.v1.models_pb2 import *
7+
from agntcy.dir.catalog.v1.models_pb2_grpc import *

dir-sdk-python/agntcy/dir_sdk/tests/test_client.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright AGNTCY Contributors (https://github.com/agntcy)
22
# SPDX-License-Identifier: Apache-2.0
3+
import json
34
import os
45
import pathlib
56
import subprocess
@@ -11,6 +12,7 @@
1112
import grpc
1213
from agntcy.dir_sdk.client import Client
1314
from agntcy.dir_sdk.models import (
15+
catalog_v1,
1416
core_v1,
1517
events_v1,
1618
routing_v1,
@@ -623,6 +625,65 @@ def test_get_verification_info(self) -> None:
623625
or verify_response.error_message == ""
624626
)
625627

628+
def test_list_agents(self) -> None:
629+
cid = self._push_catalog_fixture()
630+
self._wait_for_catalog_entry(cid)
631+
632+
response = self.client.list_agents(catalog_v1.ListAgentsRequest())
633+
634+
assert isinstance(response, catalog_v1.ListAgentsResponse)
635+
assert any(entry.identifier.find(cid) != -1 for entry in response.results)
636+
assert any(
637+
entry.display_name == "burger_seller_agent" for entry in response.results
638+
)
639+
640+
def test_get_agent(self) -> None:
641+
cid = self._push_catalog_fixture()
642+
self._wait_for_catalog_entry(cid)
643+
644+
response = self.client.get_agent(catalog_v1.GetAgentRequest(cid=cid))
645+
646+
assert isinstance(response, catalog_v1.GetAgentResponse)
647+
assert response.entry.identifier.find(cid) != -1
648+
649+
def test_get_well_known_catalog(self) -> None:
650+
self._push_catalog_fixture()
651+
652+
response = self.client.get_well_known_catalog(
653+
catalog_v1.GetWellKnownCatalogRequest()
654+
)
655+
656+
assert isinstance(response, catalog_v1.GetWellKnownCatalogResponse)
657+
assert response.catalog.spec_version
658+
assert response.catalog.host.display_name
659+
660+
def test_export_agent(self) -> None:
661+
cid = self._push_catalog_fixture()
662+
self._wait_for_catalog_entry(cid)
663+
664+
response = self.client.export_agent(
665+
catalog_v1.ExportAgentRequest(cid=cid, format="oasf")
666+
)
667+
668+
assert response.data
669+
670+
def _push_catalog_fixture(self) -> str:
671+
fixture_path = pathlib.Path(__file__).parent / "testdata" / "record_100.json"
672+
with fixture_path.open(encoding="utf-8") as fixture_file:
673+
record_data = json.load(fixture_file)
674+
record_refs = self.client.push([core_v1.Record(data=record_data)])
675+
return record_refs[0].cid
676+
677+
def _wait_for_catalog_entry(self, cid: str, timeout_sec: float = 30.0) -> None:
678+
deadline = time.time() + timeout_sec
679+
while time.time() < deadline:
680+
response = self.client.list_agents(catalog_v1.ListAgentsRequest())
681+
if any(entry.identifier.find(cid) != -1 for entry in response.results):
682+
return
683+
time.sleep(1)
684+
msg = f"catalog entry for {cid} not found within {timeout_sec}s"
685+
raise AssertionError(msg)
686+
626687
def gen_records(self, count: int, test_function_name: str) -> list[core_v1.Record]:
627688
"""
628689
Generate test records with unique names.

0 commit comments

Comments
 (0)