3737from ..features import FeatureName
3838from ..skills import models
3939from ..skills import prompt
40+ from ..skills .skill_registry import SkillRegistry
4041from .base_tool import BaseTool
4142from .base_toolset import BaseToolset
4243from .function_tool import FunctionTool
@@ -155,6 +156,20 @@ async def run_async(
155156 }
156157
157158 skill = self ._toolset ._get_skill (skill_name )
159+ if not skill and self ._toolset ._registry :
160+ try :
161+ skill = await self ._toolset ._registry .get_skill (name = skill_name )
162+ if skill :
163+ self ._toolset ._skills [skill_name ] = skill
164+ except Exception as e :
165+ logger .exception (
166+ "Failed to fetch skill '%s' from registry." , skill_name
167+ )
168+ return {
169+ "error" : f"Failed to fetch skill '{ skill_name } ' from registry: { e } " ,
170+ "error_code" : "REGISTRY_ERROR" ,
171+ }
172+
158173 if not skill :
159174 return {
160175 "error" : f"Skill '{ skill_name } ' not found." ,
@@ -771,14 +786,75 @@ async def run_async(
771786 )
772787
773788
789+ @experimental (FeatureName .SKILL_TOOLSET )
790+ class SearchSkillsTool (BaseTool ):
791+ """Tool to search for relevant skills in the registry."""
792+
793+ def __init__ (self , toolset : "SkillToolset" ):
794+ super ().__init__ (
795+ name = "search_skills" ,
796+ description = toolset ._registry .get_search_description (),
797+ )
798+ self ._toolset = toolset
799+
800+ def _get_declaration (self ) -> types .FunctionDeclaration | None :
801+ properties = {
802+ "query" : {
803+ "type" : "string" ,
804+ "description" : "Semantic or keyword search query." ,
805+ },
806+ }
807+ filter_schema = self ._toolset ._registry .get_filter_schema ()
808+ if filter_schema :
809+ properties ["filters" ] = filter_schema
810+ return types .FunctionDeclaration (
811+ name = self .name ,
812+ description = self .description ,
813+ parameters_json_schema = {
814+ "type" : "object" ,
815+ "properties" : properties ,
816+ "required" : ["query" ],
817+ },
818+ )
819+
820+ async def run_async (
821+ self , * , args : dict [str , Any ], tool_context : ToolContext
822+ ) -> Any :
823+ query = args .get ("query" )
824+ filters = args .get ("filters" )
825+
826+ if not query :
827+ return {
828+ "error" : "Argument 'query' is required." ,
829+ "error_code" : "INVALID_ARGUMENTS" ,
830+ }
831+
832+ results = await self ._toolset ._registry .search_skills (
833+ query = query , filters = filters
834+ )
835+
836+ formatted_results = []
837+ for r in results :
838+ if r .name in self ._toolset ._skills :
839+ logger .warning (
840+ "Naming conflict detected: Skill '%s' exists both locally and in"
841+ " the registry. Filtering out the registry skill." ,
842+ r .name ,
843+ )
844+ continue
845+ formatted_results .append (r .model_dump ())
846+ return formatted_results
847+
848+
774849@experimental (FeatureName .SKILL_TOOLSET )
775850class SkillToolset (BaseToolset ):
776851 """A toolset for managing and interacting with agent skills."""
777852
778853 def __init__ (
779854 self ,
780- skills : list [models .Skill ],
855+ skills : list [models .Skill ] | None = None ,
781856 * ,
857+ registry : Optional [SkillRegistry ] = None ,
782858 code_executor : Optional [BaseCodeExecutor ] = None ,
783859 script_timeout : int = _DEFAULT_SCRIPT_TIMEOUT ,
784860 additional_tools : list [ToolUnion ] | None = None ,
@@ -787,6 +863,7 @@ def __init__(
787863
788864 Args:
789865 skills: List of skills to register.
866+ registry: Optional skill registry for dynamic discovery.
790867 code_executor: Optional code executor for script execution.
791868 script_timeout: Timeout in seconds for shell script execution via
792869 subprocess.run. Defaults to 300 seconds. Does not apply to Python
@@ -796,12 +873,13 @@ def __init__(
796873
797874 # Check for duplicate skill names
798875 seen : set [str ] = set ()
799- for skill in skills :
876+ for skill in skills or [] :
800877 if skill .name in seen :
801878 raise ValueError (f"Duplicate skill name '{ skill .name } '." )
802879 seen .add (skill .name )
803880
804- self ._skills = {skill .name : skill for skill in skills }
881+ self ._skills = {skill .name : skill for skill in skills or []}
882+ self ._registry = registry
805883 self ._code_executor = code_executor
806884 self ._script_timeout = script_timeout
807885 self ._use_invocation_cache = False
@@ -824,6 +902,8 @@ def __init__(
824902 LoadSkillResourceTool (self ),
825903 RunSkillScriptTool (self ),
826904 ]
905+ if self ._registry :
906+ self ._tools .append (SearchSkillsTool (self ))
827907
828908 async def get_tools (
829909 self , readonly_context : ReadonlyContext | None = None
@@ -904,6 +984,12 @@ async def process_llm_request(
904984 skills_xml = prompt .format_skills_as_xml (skills )
905985 instructions = []
906986 instructions .append (_DEFAULT_SKILL_SYSTEM_INSTRUCTION )
987+ if self ._registry :
988+ instructions .append (
989+ "\n You can also use the `search_skills` tool to discover additional"
990+ " skills in the registry if the available skills listed below are"
991+ " not sufficient.\n "
992+ )
907993 instructions .append (skills_xml )
908994 llm_request .append_instructions (instructions )
909995
0 commit comments