/ toolsets.py
toolsets.py
  1  #!/usr/bin/env python3
  2  """
  3  Toolsets Module
  4  
  5  This module provides a flexible system for defining and managing tool aliases/toolsets.
  6  Toolsets allow you to group tools together for specific scenarios and can be composed
  7  from individual tools or other toolsets.
  8  
  9  Features:
 10  - Define custom toolsets with specific tools
 11  - Compose toolsets from other toolsets
 12  - Built-in common toolsets for typical use cases
 13  - Easy extension for new toolsets
 14  - Support for dynamic toolset resolution
 15  
 16  Usage:
 17      from toolsets import get_toolset, resolve_toolset, get_all_toolsets
 18      
 19      # Get tools for a specific toolset
 20      tools = get_toolset("research")
 21      
 22      # Resolve a toolset to get all tool names (including from composed toolsets)
 23      all_tools = resolve_toolset("full_stack")
 24  """
 25  
 26  from typing import List, Dict, Any, Set, Optional
 27  
 28  
 29  # Shared tool list for CLI and all messaging platform toolsets.
 30  # Edit this once to update all platforms simultaneously.
 31  _HERMES_CORE_TOOLS = [
 32      # Web
 33      "web_search", "web_extract",
 34      # Terminal + process management
 35      "terminal", "process",
 36      # File manipulation
 37      "read_file", "write_file", "patch", "search_files",
 38      # Vision + image generation
 39      "vision_analyze", "image_generate",
 40      # Skills
 41      "skills_list", "skill_view", "skill_manage",
 42      # Browser automation
 43      "browser_navigate", "browser_snapshot", "browser_click",
 44      "browser_type", "browser_scroll", "browser_back",
 45      "browser_press", "browser_get_images",
 46      "browser_vision", "browser_console", "browser_cdp", "browser_dialog",
 47      # Text-to-speech
 48      "text_to_speech",
 49      # Planning & memory
 50      "todo", "memory",
 51      # Session history search
 52      "session_search",
 53      # Clarifying questions
 54      "clarify",
 55      # Code execution + delegation
 56      "execute_code", "delegate_task",
 57      # Cronjob management
 58      "cronjob",
 59      # Cross-platform messaging (gated on gateway running via check_fn)
 60      "send_message",
 61      # Home Assistant smart home control (gated on HASS_TOKEN via check_fn)
 62      "ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service",
 63      # Kanban multi-agent coordination — only in schema when the agent is
 64      # spawned as a kanban worker (HERMES_KANBAN_TASK env set), otherwise
 65      # zero schema footprint. Gated via check_fn in tools/kanban_tools.py.
 66      "kanban_show", "kanban_complete", "kanban_block", "kanban_heartbeat",
 67      "kanban_comment", "kanban_create", "kanban_link",
 68  ]
 69  
 70  
 71  # Core toolset definitions
 72  # These can include individual tools or reference other toolsets
 73  TOOLSETS = {
 74      # Basic toolsets - individual tool categories
 75      "web": {
 76          "description": "Web research and content extraction tools",
 77          "tools": ["web_search", "web_extract"],
 78          "includes": []  # No other toolsets included
 79      },
 80      
 81      "search": {
 82          "description": "Web search only (no content extraction/scraping)",
 83          "tools": ["web_search"],
 84          "includes": []
 85      },
 86      
 87      "vision": {
 88          "description": "Image analysis and vision tools",
 89          "tools": ["vision_analyze"],
 90          "includes": []
 91      },
 92  
 93      "video": {
 94          "description": "Video analysis and understanding tools (opt-in, not in default toolset)",
 95          "tools": ["video_analyze"],
 96          "includes": []
 97      },
 98      
 99      "image_gen": {
100          "description": "Creative generation tools (images)",
101          "tools": ["image_generate"],
102          "includes": []
103      },
104      
105      "terminal": {
106          "description": "Terminal/command execution and process management tools",
107          "tools": ["terminal", "process"],
108          "includes": []
109      },
110      
111      "moa": {
112          "description": "Advanced reasoning and problem-solving tools",
113          "tools": ["mixture_of_agents"],
114          "includes": []
115      },
116      
117      "skills": {
118          "description": "Access, create, edit, and manage skill documents with specialized instructions and knowledge",
119          "tools": ["skills_list", "skill_view", "skill_manage"],
120          "includes": []
121      },
122      
123      "browser": {
124          "description": "Browser automation for web interaction (navigate, click, type, scroll, iframes, hold-click) with web search for finding URLs",
125          "tools": [
126              "browser_navigate", "browser_snapshot", "browser_click",
127              "browser_type", "browser_scroll", "browser_back",
128              "browser_press", "browser_get_images",
129              "browser_vision", "browser_console", "browser_cdp",
130              "browser_dialog", "web_search"
131          ],
132          "includes": []
133      },
134      
135      "cronjob": {
136          "description": "Cronjob management tool - create, list, update, pause, resume, remove, and trigger scheduled tasks",
137          "tools": ["cronjob"],
138          "includes": []
139      },
140      
141      "messaging": {
142          "description": "Cross-platform messaging: send messages to Telegram, Discord, Slack, SMS, etc.",
143          "tools": ["send_message"],
144          "includes": []
145      },
146      
147      "rl": {
148          "description": "RL training tools for running reinforcement learning on Tinker-Atropos",
149          "tools": [
150              "rl_list_environments", "rl_select_environment",
151              "rl_get_current_config", "rl_edit_config",
152              "rl_start_training", "rl_check_status",
153              "rl_stop_training", "rl_get_results",
154              "rl_list_runs", "rl_test_inference"
155          ],
156          "includes": []
157      },
158      
159      "file": {
160          "description": "File manipulation tools: read, write, patch (with fuzzy matching), and search (content + files)",
161          "tools": ["read_file", "write_file", "patch", "search_files"],
162          "includes": []
163      },
164      
165      "tts": {
166          "description": "Text-to-speech: convert text to audio with Edge TTS (free), ElevenLabs, OpenAI, or xAI",
167          "tools": ["text_to_speech"],
168          "includes": []
169      },
170      
171      "todo": {
172          "description": "Task planning and tracking for multi-step work",
173          "tools": ["todo"],
174          "includes": []
175      },
176      
177      "memory": {
178          "description": "Persistent memory across sessions (personal notes + user profile)",
179          "tools": ["memory"],
180          "includes": []
181      },
182      
183      "session_search": {
184          "description": "Search and recall past conversations with summarization",
185          "tools": ["session_search"],
186          "includes": []
187      },
188      
189      "clarify": {
190          "description": "Ask the user clarifying questions (multiple-choice or open-ended)",
191          "tools": ["clarify"],
192          "includes": []
193      },
194      
195      "code_execution": {
196          "description": "Run Python scripts that call tools programmatically (reduces LLM round trips)",
197          "tools": ["execute_code"],
198          "includes": []
199      },
200      
201      "delegation": {
202          "description": "Spawn subagents with isolated context for complex subtasks",
203          "tools": ["delegate_task"],
204          "includes": []
205      },
206  
207      # "honcho" toolset removed — Honcho is now a memory provider plugin.
208      # Tools are injected via MemoryManager, not the toolset system.
209  
210      "homeassistant": {
211          "description": "Home Assistant smart home control and monitoring",
212          "tools": ["ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service"],
213          "includes": []
214      },
215  
216      "kanban": {
217          "description": (
218              "Kanban multi-agent coordination — only active when the agent "
219              "is spawned by the kanban dispatcher (HERMES_KANBAN_TASK env "
220              "set). The dispatcher runs inside the gateway by default; see "
221              "`kanban.dispatch_in_gateway` in config.yaml. Lets workers mark "
222              "tasks done with structured handoffs, block for human input, "
223              "heartbeat during long ops, comment on threads, and (for "
224              "orchestrators) fan out into child tasks."
225          ),
226          "tools": [
227              "kanban_show", "kanban_complete", "kanban_block",
228              "kanban_heartbeat", "kanban_comment",
229              "kanban_create", "kanban_link",
230          ],
231          "includes": [],
232      },
233  
234      "discord": {
235          "description": "Discord read and participate tools (fetch messages, search members, create threads)",
236          "tools": ["discord"],
237          "includes": [],
238      },
239  
240      "discord_admin": {
241          "description": "Discord server management (list channels/roles, pin messages, assign roles)",
242          "tools": ["discord_admin"],
243          "includes": [],
244      },
245  
246      "yuanbao": {
247          "description": "Yuanbao platform tools - group info, member queries, DM, stickers",
248          "tools": [
249              "yb_query_group_info",
250              "yb_query_group_members",
251              "yb_send_dm",
252              "yb_search_sticker",
253              "yb_send_sticker",
254          ],
255          "includes": []
256      },
257  
258      "feishu_doc": {
259          "description": "Read Feishu/Lark document content",
260          "tools": ["feishu_doc_read"],
261          "includes": []
262      },
263  
264      "feishu_drive": {
265          "description": "Feishu/Lark document comment operations (list, reply, add)",
266          "tools": [
267              "feishu_drive_list_comments", "feishu_drive_list_comment_replies",
268              "feishu_drive_reply_comment", "feishu_drive_add_comment",
269          ],
270          "includes": []
271      },
272  
273      "spotify": {
274          "description": "Native Spotify playback, search, playlist, album, and library tools",
275          "tools": [
276              "spotify_playback", "spotify_devices", "spotify_queue", "spotify_search",
277              "spotify_playlists", "spotify_albums", "spotify_library",
278          ],
279          "includes": []
280      },
281  
282  
283      # Scenario-specific toolsets
284      
285      "debugging": {
286          "description": "Debugging and troubleshooting toolkit",
287          "tools": ["terminal", "process"],
288          "includes": ["web", "file"]  # For searching error messages and solutions, and file operations
289      },
290      
291      "safe": {
292          "description": "Safe toolkit without terminal access",
293          "tools": [],
294          "includes": ["web", "vision", "image_gen"]
295      },
296      
297      # ==========================================================================
298      # Full Hermes toolsets (CLI + messaging platforms)
299      #
300      # All platforms share the same core tools (including send_message,
301      # which is gated on gateway running via its check_fn).
302      # ==========================================================================
303  
304      "hermes-acp": {
305          "description": "Editor integration (VS Code, Zed, JetBrains) — coding-focused tools without messaging, audio, or clarify UI",
306          "tools": [
307              "web_search", "web_extract",
308              "terminal", "process",
309              "read_file", "write_file", "patch", "search_files",
310              "vision_analyze",
311              "skills_list", "skill_view", "skill_manage",
312              "browser_navigate", "browser_snapshot", "browser_click",
313              "browser_type", "browser_scroll", "browser_back",
314              "browser_press", "browser_get_images",
315              "browser_vision", "browser_console", "browser_cdp", "browser_dialog",
316              "todo", "memory",
317              "session_search",
318              "execute_code", "delegate_task",
319          ],
320          "includes": []
321      },
322  
323      "hermes-api-server": {
324          "description": "OpenAI-compatible API server — full agent tools accessible via HTTP (no interactive UI tools like clarify or send_message)",
325          "tools": [
326              # Web
327              "web_search", "web_extract",
328              # Terminal + process management
329              "terminal", "process",
330              # File manipulation
331              "read_file", "write_file", "patch", "search_files",
332              # Vision + image generation
333              "vision_analyze", "image_generate",
334              # Skills
335              "skills_list", "skill_view", "skill_manage",
336              # Browser automation
337              "browser_navigate", "browser_snapshot", "browser_click",
338              "browser_type", "browser_scroll", "browser_back",
339              "browser_press", "browser_get_images",
340              "browser_vision", "browser_console", "browser_cdp", "browser_dialog",
341              # Planning & memory
342              "todo", "memory",
343              # Session history search
344              "session_search",
345              # Code execution + delegation
346              "execute_code", "delegate_task",
347              # Cronjob management
348              "cronjob",
349              # Home Assistant smart home control (gated on HASS_TOKEN via check_fn)
350              "ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service",
351  
352          ],
353          "includes": []
354      },
355      
356      "hermes-cli": {
357          "description": "Full interactive CLI toolset - all default tools plus cronjob management",
358          "tools": _HERMES_CORE_TOOLS,
359          "includes": []
360      },
361  
362      "hermes-cron": {
363          # Mirrors hermes-cli so cron's "default" toolset is the same set of
364          # core tools users see interactively — then `hermes tools` filters
365          # them down per the platform config. _DEFAULT_OFF_TOOLSETS (moa,
366          # homeassistant, rl) are excluded by _get_platform_tools() unless
367          # the user explicitly enables them.
368          "description": "Default cron toolset - same core tools as hermes-cli; gated by `hermes tools`",
369          "tools": _HERMES_CORE_TOOLS,
370          "includes": []
371      },
372  
373      "hermes-telegram": {
374          "description": "Telegram bot toolset - full access for personal use (terminal has safety checks)",
375          "tools": _HERMES_CORE_TOOLS,
376          "includes": []
377      },
378      
379      "hermes-discord": {
380          "description": "Discord bot toolset - full access (terminal has safety checks via dangerous command approval)",
381          "tools": _HERMES_CORE_TOOLS + [
382              "discord",
383              "discord_admin",
384          ],
385          "includes": []
386      },
387      
388      "hermes-whatsapp": {
389          "description": "WhatsApp bot toolset - similar to Telegram (personal messaging, more trusted)",
390          "tools": _HERMES_CORE_TOOLS,
391          "includes": []
392      },
393      
394      "hermes-slack": {
395          "description": "Slack bot toolset - full access for workspace use (terminal has safety checks)",
396          "tools": _HERMES_CORE_TOOLS,
397          "includes": []
398      },
399      
400      "hermes-signal": {
401          "description": "Signal bot toolset - encrypted messaging platform (full access)",
402          "tools": _HERMES_CORE_TOOLS,
403          "includes": []
404      },
405  
406      "hermes-bluebubbles": {
407          "description": "BlueBubbles iMessage bot toolset - Apple iMessage via local BlueBubbles server",
408          "tools": _HERMES_CORE_TOOLS,
409          "includes": []
410      },
411  
412      "hermes-homeassistant": {
413          "description": "Home Assistant bot toolset - smart home event monitoring and control",
414          "tools": _HERMES_CORE_TOOLS,
415          "includes": []
416      },
417  
418      "hermes-email": {
419          "description": "Email bot toolset - interact with Hermes via email (IMAP/SMTP)",
420          "tools": _HERMES_CORE_TOOLS,
421          "includes": []
422      },
423  
424      "hermes-mattermost": {
425          "description": "Mattermost bot toolset - self-hosted team messaging (full access)",
426          "tools": _HERMES_CORE_TOOLS,
427          "includes": []
428      },
429  
430      "hermes-matrix": {
431          "description": "Matrix bot toolset - decentralized encrypted messaging (full access)",
432          "tools": _HERMES_CORE_TOOLS,
433          "includes": []
434      },
435  
436      "hermes-dingtalk": {
437          "description": "DingTalk bot toolset - enterprise messaging platform (full access)",
438          "tools": _HERMES_CORE_TOOLS,
439          "includes": []
440      },
441  
442      "hermes-feishu": {
443          "description": "Feishu/Lark bot toolset - enterprise messaging via Feishu/Lark (full access)",
444          "tools": _HERMES_CORE_TOOLS + [
445              "feishu_doc_read",
446              "feishu_drive_list_comments",
447              "feishu_drive_list_comment_replies",
448              "feishu_drive_reply_comment",
449              "feishu_drive_add_comment",
450          ],
451          "includes": []
452      },
453  
454      "hermes-weixin": {
455          "description": "Weixin bot toolset - personal WeChat messaging via iLink (full access)",
456          "tools": _HERMES_CORE_TOOLS,
457          "includes": []
458      },
459  
460      "hermes-qqbot": {
461          "description": "QQBot toolset - QQ messaging via Official Bot API v2 (full access)",
462          "tools": _HERMES_CORE_TOOLS,
463          "includes": []
464      },
465  
466      "hermes-wecom": {
467          "description": "WeCom bot toolset - enterprise WeChat messaging (full access)",
468          "tools": _HERMES_CORE_TOOLS,
469          "includes": []
470      },
471  
472      "hermes-wecom-callback": {
473          "description": "WeCom callback toolset - enterprise self-built app messaging (full access)",
474          "tools": _HERMES_CORE_TOOLS,
475          "includes": []
476      },
477  
478      "hermes-yuanbao": {
479          "description": "Yuanbao Bot 元宝消息平台工具集 - 群信息、成员查询、私聊、贴纸表情",
480          "tools": _HERMES_CORE_TOOLS + [
481              "yb_query_group_info",
482              "yb_query_group_members",
483              "yb_send_dm",
484              "yb_search_sticker",
485              "yb_send_sticker",
486          ],
487          "module": "tools.yuanbao_tools",
488          "includes": []
489      },
490  
491      "hermes-sms": {
492          "description": "SMS bot toolset - interact with Hermes via SMS (Twilio)",
493          "tools": _HERMES_CORE_TOOLS,
494          "includes": []
495      },
496  
497      "hermes-webhook": {
498          "description": "Webhook toolset - receive and process external webhook events",
499          "tools": _HERMES_CORE_TOOLS,
500          "includes": []
501      },
502  
503      "hermes-gateway": {
504          "description": "Gateway toolset - union of all messaging platform tools",
505          "tools": [],
506          "includes": ["hermes-telegram", "hermes-discord", "hermes-whatsapp", "hermes-slack", "hermes-signal", "hermes-bluebubbles", "hermes-homeassistant", "hermes-email", "hermes-sms", "hermes-mattermost", "hermes-matrix", "hermes-dingtalk", "hermes-feishu", "hermes-wecom", "hermes-wecom-callback", "hermes-weixin", "hermes-qqbot", "hermes-webhook", "hermes-yuanbao"]
507      }
508  }
509  
510  
511  
512  def get_toolset(name: str) -> Optional[Dict[str, Any]]:
513      """
514      Get a toolset definition by name.
515      
516      Args:
517          name (str): Name of the toolset
518          
519      Returns:
520          Dict: Toolset definition with description, tools, and includes
521          None: If toolset not found
522      """
523      toolset = TOOLSETS.get(name)
524      if toolset:
525          return toolset
526  
527      try:
528          from tools.registry import registry
529      except Exception:
530          return None
531  
532      registry_toolset = name
533      description = f"Plugin toolset: {name}"
534      alias_target = registry.get_toolset_alias_target(name)
535  
536      if name not in _get_plugin_toolset_names():
537          registry_toolset = alias_target
538          if not registry_toolset:
539              return None
540          description = f"MCP server '{name}' tools"
541      else:
542          reverse_aliases = {
543              canonical: alias
544              for alias, canonical in _get_registry_toolset_aliases().items()
545              if alias not in TOOLSETS
546          }
547          alias = reverse_aliases.get(name)
548          if alias:
549              description = f"MCP server '{alias}' tools"
550  
551      return {
552          "description": description,
553          "tools": registry.get_tool_names_for_toolset(registry_toolset),
554          "includes": [],
555      }
556  
557  
558  def resolve_toolset(name: str, visited: Set[str] = None) -> List[str]:
559      """
560      Recursively resolve a toolset to get all tool names.
561      
562      This function handles toolset composition by recursively resolving
563      included toolsets and combining all tools.
564      
565      Args:
566          name (str): Name of the toolset to resolve
567          visited (Set[str]): Set of already visited toolsets (for cycle detection)
568          
569      Returns:
570          List[str]: List of all tool names in the toolset
571      """
572      if visited is None:
573          visited = set()
574      
575      # Special aliases that represent all tools across every toolset
576      # This ensures future toolsets are automatically included without changes.
577      if name in {"all", "*"}:
578          all_tools: Set[str] = set()
579          for toolset_name in get_toolset_names():
580              # Use a fresh visited set per branch to avoid cross-branch contamination
581              resolved = resolve_toolset(toolset_name, visited.copy())
582              all_tools.update(resolved)
583          return sorted(all_tools)
584  
585      # Check for cycles / already-resolved (diamond deps).
586      # Silently return [] — either this is a diamond (not a bug, tools already
587      # collected via another path) or a genuine cycle (safe to skip).
588      if name in visited:
589          return []
590  
591      visited.add(name)
592  
593      # Get toolset definition
594      toolset = get_toolset(name)
595      if not toolset:
596          # Auto-generate a toolset for plugin platforms (hermes-<name>).
597          # Gives them _HERMES_CORE_TOOLS plus any tools the plugin registered
598          # into a toolset matching the platform name.
599          if name.startswith("hermes-"):
600              platform_name = name[len("hermes-"):]
601              try:
602                  from gateway.platform_registry import platform_registry
603                  if platform_registry.is_registered(platform_name):
604                      plugin_tools = set(_HERMES_CORE_TOOLS)
605                      try:
606                          from tools.registry import registry
607                          plugin_tools.update(
608                              e.name for e in registry._tools.values()
609                              if e.toolset == platform_name
610                          )
611                      except Exception:
612                          pass
613                      return list(plugin_tools)
614              except Exception:
615                  pass
616  
617          return []
618  
619      # Collect direct tools
620      tools = set(toolset.get("tools", []))
621  
622      # Recursively resolve included toolsets, sharing the visited set across
623      # sibling includes so diamond dependencies are only resolved once and
624      # cycle warnings don't fire multiple times for the same cycle.
625      for included_name in toolset.get("includes", []):
626          included_tools = resolve_toolset(included_name, visited)
627          tools.update(included_tools)
628      
629      return sorted(tools)
630  
631  
632  def resolve_multiple_toolsets(toolset_names: List[str]) -> List[str]:
633      """
634      Resolve multiple toolsets and combine their tools.
635      
636      Args:
637          toolset_names (List[str]): List of toolset names to resolve
638          
639      Returns:
640          List[str]: Combined list of all tool names (deduplicated)
641      """
642      all_tools = set()
643      
644      for name in toolset_names:
645          tools = resolve_toolset(name)
646          all_tools.update(tools)
647      
648      return sorted(all_tools)
649  
650  
651  def _get_plugin_toolset_names() -> Set[str]:
652      """Return toolset names registered by plugins (from the tool registry).
653  
654      These are toolsets that exist in the registry but not in the static
655      ``TOOLSETS`` dict — i.e. they were added by plugins at load time.
656      """
657      try:
658          from tools.registry import registry
659          return {
660              toolset_name
661              for toolset_name in registry.get_registered_toolset_names()
662              if toolset_name not in TOOLSETS
663          }
664      except Exception:
665          return set()
666  
667  
668  def _get_registry_toolset_aliases() -> Dict[str, str]:
669      """Return explicit toolset aliases registered in the live registry."""
670      try:
671          from tools.registry import registry
672          return registry.get_registered_toolset_aliases()
673      except Exception:
674          return {}
675  
676  
677  def get_all_toolsets() -> Dict[str, Dict[str, Any]]:
678      """
679      Get all available toolsets with their definitions.
680  
681      Includes both statically-defined toolsets and plugin-registered ones.
682      
683      Returns:
684          Dict: All toolset definitions
685      """
686      result = dict(TOOLSETS)
687      aliases = _get_registry_toolset_aliases()
688      for ts_name in _get_plugin_toolset_names():
689          display_name = ts_name
690          for alias, canonical in aliases.items():
691              if canonical == ts_name and alias not in TOOLSETS:
692                  display_name = alias
693                  break
694          if display_name in result:
695              continue
696          toolset = get_toolset(display_name)
697          if toolset:
698              result[display_name] = toolset
699      return result
700  
701  
702  def get_toolset_names() -> List[str]:
703      """
704      Get names of all available toolsets (excluding aliases).
705  
706      Includes plugin-registered toolset names.
707      
708      Returns:
709          List[str]: List of toolset names
710      """
711      names = set(TOOLSETS.keys())
712      aliases = _get_registry_toolset_aliases()
713      for ts_name in _get_plugin_toolset_names():
714          for alias, canonical in aliases.items():
715              if canonical == ts_name and alias not in TOOLSETS:
716                  names.add(alias)
717                  break
718          else:
719              names.add(ts_name)
720      return sorted(names)
721  
722  
723  
724  
725  def validate_toolset(name: str) -> bool:
726      """
727      Check if a toolset name is valid.
728      
729      Args:
730          name (str): Toolset name to validate
731          
732      Returns:
733          bool: True if valid, False otherwise
734      """
735      # Accept special alias names for convenience
736      if name in {"all", "*"}:
737          return True
738      if name in TOOLSETS:
739          return True
740      if name in _get_plugin_toolset_names():
741          return True
742      return name in _get_registry_toolset_aliases()
743  
744  
745  def create_custom_toolset(
746      name: str,
747      description: str,
748      tools: List[str] = None,
749      includes: List[str] = None
750  ) -> None:
751      """
752      Create a custom toolset at runtime.
753      
754      Args:
755          name (str): Name for the new toolset
756          description (str): Description of the toolset
757          tools (List[str]): Direct tools to include
758          includes (List[str]): Other toolsets to include
759      """
760      TOOLSETS[name] = {
761          "description": description,
762          "tools": tools or [],
763          "includes": includes or []
764      }
765  
766  
767  
768  
769  def get_toolset_info(name: str) -> Dict[str, Any]:
770      """
771      Get detailed information about a toolset including resolved tools.
772      
773      Args:
774          name (str): Toolset name
775          
776      Returns:
777          Dict: Detailed toolset information
778      """
779      toolset = get_toolset(name)
780      if not toolset:
781          return None
782      
783      resolved_tools = resolve_toolset(name)
784      
785      return {
786          "name": name,
787          "description": toolset["description"],
788          "direct_tools": toolset["tools"],
789          "includes": toolset["includes"],
790          "resolved_tools": resolved_tools,
791          "tool_count": len(resolved_tools),
792          "is_composite": bool(toolset["includes"])
793      }
794  
795  
796  
797  
798  if __name__ == "__main__":
799      print("Toolsets System Demo")
800      print("=" * 60)
801      
802      print("\nAvailable Toolsets:")
803      print("-" * 40)
804      for name, toolset in get_all_toolsets().items():
805          info = get_toolset_info(name)
806          composite = "[composite]" if info["is_composite"] else "[leaf]"
807          print(f"  {composite} {name:20} - {toolset['description']}")
808          print(f"     Tools: {len(info['resolved_tools'])} total")
809      
810      print("\nToolset Resolution Examples:")
811      print("-" * 40)
812      for name in ["web", "terminal", "safe", "debugging"]:
813          tools = resolve_toolset(name)
814          print(f"\n  {name}:")
815          print(f"    Resolved to {len(tools)} tools: {', '.join(sorted(tools))}")
816      
817      print("\nMultiple Toolset Resolution:")
818      print("-" * 40)
819      combined = resolve_multiple_toolsets(["web", "vision", "terminal"])
820      print("  Combining ['web', 'vision', 'terminal']:")
821      print(f"    Result: {', '.join(sorted(combined))}")
822      
823      print("\nCustom Toolset Creation:")
824      print("-" * 40)
825      create_custom_toolset(
826          name="my_custom",
827          description="My custom toolset for specific tasks",
828          tools=["web_search"],
829          includes=["terminal", "vision"]
830      )
831      custom_info = get_toolset_info("my_custom")
832      print("  Created 'my_custom' toolset:")
833      print(f"    Description: {custom_info['description']}")
834      print(f"    Resolved tools: {', '.join(custom_info['resolved_tools'])}")