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.

@codebee-aoki codebee-aoki left a comment

Copy link
Copy Markdown
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

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
Contributor

Choose a reason for hiding this comment

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

_PublishedSkillsAccessor is unused.

@codebee-aoki

Copy link
Copy Markdown
Contributor

Hi @ArulJerald, checking in on this one. The review points above are still open, the v1alpha base URL in particular (the Console snippet 404s against the real service without it), and the branch now conflicts with main after the AgentRegistry changes on 2026-09-24 (9115d61, 345a1a8). Are you planning to pick this up?

If I don't hear back within a week (by 2026-10-07), I'll open a follow-up PR that keeps your published_skills / project-alias design, addresses the five points, and credits you as co-author. Happy to review instead if you'd rather push the fixes yourself.

…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
@ArulJerald
ArulJerald force-pushed the feat/agent-registry-published-skills-7137 branch from 557012d to da29b23 Compare September 30, 2026 16:21
@ArulJerald

Copy link
Copy Markdown
Author

Hi @codebee-aoki , thanks for checking in and for the detailed review!

I have rebased the branch onto latest main to resolve the conflicts and addressed all five points:

  1. v1alpha endpoint: Routed skill metadata and media requests through a dedicated v1alpha base URL with mTLS and AGENT_REGISTRY_ENDPOINT environment override support (mirroring GCPSkillRegistry).
  2. Path segment validation: Validated each segment of projects/{project}/locations/{location}/skills/{skill_id} using the safe registry ID regex rule (^[a-z0-9]+(?:[._-][a-z0-9]+)*$, max 256) before sending any request, rejecting .., ?, %, and uppercase characters.
  3. Redirect handling: Removed the manual 3xx re-issuance fallback and rely standardly on allow_redirects=True.
  4. default_revision format: Enforced projects/... format, rejecting arbitrary absolute URLs.
  5. Project alias validation: Now raises ValueError if both project_id and project are provided with differing values.
  6. Cleanup & tests: Removed _PublishedSkillsAccessor, updated and added unit tests covering the v1alpha URLs, mTLS, AGENT_REGISTRY_ENDPOINT override, segment validations, and project mismatch. All 107 tests pass cleanly.

The commit has been updated on the branch.

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