Skip to content

feat(agent_registry): support published_skills accessor and project alias - #7174

Open
ArulJerald wants to merge 1 commit into
google:mainfrom
ArulJerald:feat/agent-registry-published-skills-7137
Open

ArulJerald wants to merge 1 commit into
google:mainfrom
ArulJerald:feat/agent-registry-published-skills-7137

Conversation

@ArulJerald

Copy link
Copy Markdown

Link to Issue or Description of Change

1. Link to an existing issue (if applicable):

registry = AgentRegistry(project="my-project", location="global")
skill = registry.published_skills.get(name="projects/.../locations/.../skills/...")

In ADK, AgentRegistry.__init__ previously only accepted project_id, resulting in a TypeError: unexpected keyword argument 'project'. Additionally, AgentRegistry lacked a published_skills accessor and had no method to fetch, download, and deserialize published skills from the registry into Skill models for use with SkillToolset.
Solution:

  1. Added project: str | None = None parameter to AgentRegistry.__init__ as an alias for project_id, along with a @property def project(self) -> str | None.
  2. Created PublishedSkills accessor exposed via registry.published_skills with synchronous get(name: str) -> Skill (and added a top-level get_published_skill convenience method).
  3. Validated full resource names matching projects/{project}/locations/{location}/skills/{skill_id}.
  4. Resolved the skill's default revision and downloaded the media archive using AuthorizedSession (handling 302/307 redirects to media URLs).
  5. Loaded the zip archive into a google.adk.skills.models.Skill instance ready to pass to SkillToolset(skills=[...]).

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.
    Summary of passing pytest results:
  • uv run pytest tests/unittests/integrations/agent_registry/test_agent_registry.py: 72 passed in 1.96s
  • Added test coverage for:
    • Initializing AgentRegistry(project="...", location="...") and project_id precedence.
    • Calling registry.published_skills.get(name=...) with mock metadata and media responses.
    • Calling with positional arguments and via registry.get_published_skill(...).
    • Following HTTP 302 media redirects to signed Cloud Storage download URLs.
    • Passing returned Skill directly to SkillToolset(skills=[skill]).
    • Raising errors on invalid resource names, missing default revision, and HTTP errors (404/500).
      Manual End-to-End (E2E) Tests:
      N/A - verified via unit tests with mocked API endpoints and AuthorizedSession behavior.

Checklist

  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • Any dependent changes have been merged and published in downstream modules.

…lias

Allow AgentRegistry(project=...) as an alias for project_id to align
with Google Cloud Console snippets, and introduce a published_skills
accessor that fetches and loads published skills into Skill objects.

Closes google#7137

@codebee-aoki codebee-aoki left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ArulJerald. The API shape matches what #7137 asks for: synchronous, takes the full resource name, returns a Skill that drops into SkillToolset(skills=[...]), and project= is accepted. 72 unit tests pass locally. Unfortunately it does not work against the real service yet.

Blocker: wrong API version. Running the Console snippet verbatim against a real catalog fails with HTTP 404 on https://agentregistry.googleapis.com/v1/projects/.../skills/.... AgentRegistry uses the /v1 base, but skills only exist in v1alpha: the public discovery document for v1 lists agents, aiApplications, bindings, endpoints, mcpServers, operations and services under locations, while v1alpha adds publishers and skills (GCPSkillRegistry already uses v1alpha). With only the base URL switched to v1alpha, the snippet runs end to end in about 3 seconds and the Google-published skill cloud.google.com-google-cloud-networking-observability loads and works in SkillToolset. The unit tests do not catch this because HTTP is mocked and no test asserts the requested URL. Suggest a dedicated v1alpha base for the skill calls (mirroring GCPSkillRegistry, including the mTLS template and the AGENT_REGISTRY_ENDPOINT override) and asserting the URL in the tests.

The remaining points are inline.

)


_SKILL_RESOURCE_NAME_PATTERN = re.compile(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[^/]+ accepts .., ? and %. Against the real service, .../skills/.. sends an authorized GET to /v1alpha/projects/<p>/locations/global/ (200), and .../skills/x?alt=media injects the query parameter into the metadata call. Please validate each segment before any request, e.g. with the safe-id rule from #7138 (^[a-z0-9]+(?:[._-][a-z0-9]+)*$, max 256).

params=params,
allow_redirects=True,
)
if 300 <= response.status_code < 400 and (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow_redirects=True already follows redirects, and requests drops Authorization when the host changes. This fallback re-issues the request through AuthorizedSession, which attaches the bearer token to whatever Location says (the new redirect test exercises exactly this with storage.googleapis.com). In the real run the redirect was same-host (/download/v1alpha/...) and this branch was never reached. Please remove it.

if not default_revision:
raise ValueError(f"Skill '{name}' does not contain default revision.")

if default_revision.startswith("http://") or default_revision.startswith(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns defaultRevision as projects/.... Sending an authorized request to an arbitrary absolute URL taken from a response body is unnecessary; keeping only the projects/ form (as GCPSkillRegistry does) is simpler and safer.

project: Optional alias for project_id.
"""
self.project_id = project_id
self.project_id = project_id or project

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both project_id and project are given and differ, this silently picks project_id. Please raise ValueError instead.

return self._registry._fetch_published_skill_sync(name)


_PublishedSkillsAccessor = PublishedSkills

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PublishedSkillsAccessor is unused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(agent_registry): the Console code snippet uses AgentRegistry(project=...).published_skills.get(name=...), which does not exist in ADK

3 participants