Merge branch 'simp/r3-01-E' into simp/r3-01

This commit is contained in:
Teknium
2026-09-02 19:35:24 -07:00
4 changed files with 611 additions and 1387 deletions
+271 -563
View File
File diff suppressed because it is too large Load Diff
+219 -372
View File
File diff suppressed because it is too large Load Diff
+56 -304
View File
@@ -1,22 +1,8 @@
#!/usr/bin/env python3
"""
Toolset Distributions Module
"""Toolset distributions for batch data-generation runs.
This module defines distributions of toolsets for data generation runs.
Each distribution specifies which toolsets should be used and their probability
of being selected for any given prompt during the batch processing.
A distribution is a dictionary mapping toolset names to their selection probability (%).
Probabilities should sum to 100, but the system will normalize if they don't.
Usage:
from toolset_distributions import get_distribution, list_distributions
# Get a specific distribution
dist = get_distribution("image_gen")
# List all available distributions
all_dists = list_distributions()
A distribution maps toolset names to the % chance each is enabled for a prompt
(sampled independently, so several toolsets can be active at once).
"""
from typing import Dict, List, Optional
@@ -24,335 +10,101 @@ import random
from toolsets import validate_toolset
# Distribution definitions
# Each key is a distribution name, and the value is a dict of toolset_name: probability_percentage
def _dist(description: str, **toolsets: int) -> Dict[str, object]:
return {"description": description, "toolsets": toolsets}
DISTRIBUTIONS = {
# Default: All tools available 100% of the time
"default": {
"description": "All available tools, all the time",
"toolsets": {
"web": 100,
"vision": 100,
"image_gen": 100,
"terminal": 100,
"file": 100,
"browser": 100
}
},
# Image generation focused distribution
"image_gen": {
"description": "Heavy focus on image generation with vision and web support",
"toolsets": {
"image_gen": 90, # 80% chance of image generation tools
"vision": 90, # 60% chance of vision tools
"web": 55, # 40% chance of web tools
"terminal": 45
}
},
# Research-focused distribution
"research": {
"description": "Web research with vision analysis and reasoning",
"toolsets": {
"web": 90, # 90% chance of web tools
"browser": 70, # 70% chance of browser tools for deep research
"vision": 50, # 50% chance of vision tools
"terminal": 10 # 10% chance of terminal tools
}
},
# Scientific problem solving focused distribution
"science": {
"description": "Scientific research with web, terminal, file, and browser capabilities",
"toolsets": {
"web": 94, # 94% chance of web tools
"terminal": 94, # 94% chance of terminal tools
"file": 94, # 94% chance of file tools
"vision": 65, # 65% chance of vision tools
"browser": 50, # 50% chance of browser for accessing papers/databases
"image_gen": 15 # 15% chance of image generation tools
}
},
# Development-focused distribution
"development": {
"description": "Terminal, file tools, and reasoning with occasional web lookup",
"toolsets": {
"terminal": 80, # 80% chance of terminal tools
"file": 80, # 80% chance of file tools (read, write, patch, search)
"web": 30, # 30% chance of web tools
"vision": 10 # 10% chance of vision tools
}
},
# Safe mode (no terminal)
"safe": {
"description": "All tools except terminal for safety",
"toolsets": {
"web": 80,
"browser": 70, # Browser is safe (no local filesystem access)
"vision": 60,
"image_gen": 60
}
},
# Balanced distribution
"balanced": {
"description": "Equal probability of all toolsets",
"toolsets": {
"web": 50,
"vision": 50,
"image_gen": 50,
"terminal": 50,
"file": 50,
"browser": 50
}
},
# Minimal (web only)
"minimal": {
"description": "Only web tools for basic research",
"toolsets": {
"web": 100
}
},
# Terminal only
"terminal_only": {
"description": "Terminal and file tools for code execution tasks",
"toolsets": {
"terminal": 100,
"file": 100
}
},
# Terminal + web (common for coding tasks that need docs)
"terminal_web": {
"description": "Terminal and file tools with web search for documentation lookup",
"toolsets": {
"terminal": 100,
"file": 100,
"web": 100
}
},
# Creative (vision + image generation)
"creative": {
"description": "Image generation and vision analysis focus",
"toolsets": {
"image_gen": 90,
"vision": 90,
"web": 30
}
},
# Reasoning heavy
"reasoning": {
"description": "Heavy research/reasoning distribution with minimal other tools",
"toolsets": {
"web": 90,
"file": 60,
"terminal": 20
}
},
# Browser-based web interaction
"browser_use": {
"description": "Full browser-based web interaction with search, vision, and page control",
"toolsets": {
"browser": 100, # All browser tools always available
"web": 80, # Web search for finding URLs and quick lookups
"vision": 70 # Vision analysis for images found on pages
}
},
# Browser only (no other tools)
"browser_only": {
"description": "Only browser automation tools for pure web interaction tasks",
"toolsets": {
"browser": 100
}
},
# Browser-focused tasks distribution (for browser-use-tasks.jsonl)
"browser_tasks": {
"description": "Browser-focused distribution (browser toolset includes web_search for finding URLs since Google blocks direct browser searches)",
"toolsets": {
"browser": 97, # 97% - browser tools (includes web_search) almost always available
"vision": 12, # 12% - vision analysis occasionally
"terminal": 15 # 15% - terminal occasionally for local operations
}
},
# Terminal-focused tasks distribution (for nous-terminal-tasks.jsonl)
"terminal_tasks": {
"description": "Terminal-focused distribution with high terminal/file availability, occasional other tools",
"toolsets": {
"terminal": 97, # 97% - terminal almost always available
"file": 97, # 97% - file tools almost always available
"web": 97, # 15% - web search/scrape for documentation
"browser": 75, # 10% - browser occasionally for web interaction
"vision": 50, # 8% - vision analysis rarely
"image_gen": 10 # 3% - image generation very rarely
}
},
# Mixed browser+terminal tasks distribution (for mixed-browser-terminal-tasks.jsonl)
"mixed_tasks": {
"description": "Mixed distribution with high browser, terminal, and file availability for complex tasks",
"toolsets": {
"browser": 92, # 92% - browser tools highly available
"terminal": 92, # 92% - terminal highly available
"file": 92, # 92% - file tools highly available
"web": 35, # 35% - web search/scrape fairly common
"vision": 15, # 15% - vision analysis occasionally
"image_gen": 15 # 15% - image generation occasionally
}
}
"default": _dist("All available tools, all the time",
web=100, vision=100, image_gen=100, terminal=100, file=100, browser=100),
"image_gen": _dist("Heavy focus on image generation with vision and web support",
image_gen=90, vision=90, web=55, terminal=45),
"research": _dist("Web research with vision analysis and reasoning",
web=90, browser=70, vision=50, terminal=10),
"science": _dist("Scientific research with web, terminal, file, and browser capabilities",
web=94, terminal=94, file=94, vision=65, browser=50, image_gen=15),
"development": _dist("Terminal, file tools, and reasoning with occasional web lookup",
terminal=80, file=80, web=30, vision=10),
"safe": _dist("All tools except terminal for safety",
web=80, browser=70, vision=60, image_gen=60),
"balanced": _dist("Equal probability of all toolsets",
web=50, vision=50, image_gen=50, terminal=50, file=50, browser=50),
"minimal": _dist("Only web tools for basic research", web=100),
"terminal_only": _dist("Terminal and file tools for code execution tasks", terminal=100, file=100),
"terminal_web": _dist("Terminal and file tools with web search for documentation lookup",
terminal=100, file=100, web=100),
"creative": _dist("Image generation and vision analysis focus", image_gen=90, vision=90, web=30),
"reasoning": _dist("Heavy research/reasoning distribution with minimal other tools",
web=90, file=60, terminal=20),
"browser_use": _dist("Full browser-based web interaction with search, vision, and page control",
browser=100, web=80, vision=70),
"browser_only": _dist("Only browser automation tools for pure web interaction tasks", browser=100),
# browser-use-tasks.jsonl: the browser toolset includes web_search since Google blocks direct browser searches
"browser_tasks": _dist(
"Browser-focused distribution (browser toolset includes web_search for finding URLs since Google blocks direct browser searches)",
browser=97, vision=12, terminal=15,
),
# nous-terminal-tasks.jsonl
"terminal_tasks": _dist(
"Terminal-focused distribution with high terminal/file availability, occasional other tools",
terminal=97, file=97, web=97, browser=75, vision=50, image_gen=10,
),
# mixed-browser-terminal-tasks.jsonl
"mixed_tasks": _dist(
"Mixed distribution with high browser, terminal, and file availability for complex tasks",
browser=92, terminal=92, file=92, web=35, vision=15, image_gen=15,
),
}
def get_distribution(name: str) -> Optional[Dict[str, any]]:
"""
Get a toolset distribution by name.
Args:
name (str): Name of the distribution
Returns:
Dict: Distribution definition with description and toolsets
None: If distribution not found
"""
"""Distribution definition (description + toolsets), or None if unknown."""
return DISTRIBUTIONS.get(name)
def list_distributions() -> Dict[str, Dict]:
"""
List all available distributions.
Returns:
Dict: All distribution definitions
"""
return DISTRIBUTIONS.copy()
def sample_toolsets_from_distribution(distribution_name: str) -> List[str]:
"""
Sample toolsets based on a distribution's probabilities.
Each toolset in the distribution has a % chance of being included.
This allows multiple toolsets to be active simultaneously.
Args:
distribution_name (str): Name of the distribution to sample from
Returns:
List[str]: List of sampled toolset names
Raises:
ValueError: If distribution name is not found
"""Sample toolset names, each included independently with its % probability.
Falls back to the highest-probability toolset when nothing was rolled.
Raises ValueError for an unknown distribution.
"""
dist = get_distribution(distribution_name)
if not dist:
raise ValueError(f"Unknown distribution: {distribution_name}")
# Sample each toolset independently based on its probability
selected_toolsets = []
for toolset_name, probability in dist["toolsets"].items():
# Validate toolset exists
if not validate_toolset(toolset_name):
print(f"⚠️ Warning: Toolset '{toolset_name}' in distribution '{distribution_name}' is not valid")
continue
# Roll the dice - if random value is less than probability, include this toolset
if random.random() * 100 < probability:
selected_toolsets.append(toolset_name)
# If no toolsets were selected (can happen with low probabilities),
# ensure at least one toolset is selected by picking the highest probability one
if not selected_toolsets and dist["toolsets"]:
# Find toolset with highest probability
highest_prob_toolset = max(dist["toolsets"].items(), key=lambda x: x[1])[0]
if validate_toolset(highest_prob_toolset):
selected_toolsets.append(highest_prob_toolset)
return selected_toolsets
def validate_distribution(distribution_name: str) -> bool:
"""
Check if a distribution name is valid.
Args:
distribution_name (str): Distribution name to validate
Returns:
bool: True if valid, False otherwise
"""
return distribution_name in DISTRIBUTIONS
def print_distribution_info(distribution_name: str) -> None:
"""
Print detailed information about a distribution.
Args:
distribution_name (str): Distribution name
"""
"""Print a distribution's description and toolset probabilities (highest first)."""
dist = get_distribution(distribution_name)
if not dist:
print(f"❌ Unknown distribution: {distribution_name}")
return
print(f"\n📊 Distribution: {distribution_name}")
print(f" Description: {dist['description']}")
print(" Toolsets:")
for toolset, prob in sorted(dist["toolsets"].items(), key=lambda x: x[1], reverse=True):
print(f" • {toolset:15} : {prob:3}% chance")
if __name__ == "__main__":
"""
Demo and testing of the distributions system
"""
print("📊 Toolset Distributions Demo")
print("=" * 60)
# List all distributions
print("\n📋 Available Distributions:")
print("-" * 40)
for name, dist in list_distributions().items():
print(f"\n {name}:")
print(f" {dist['description']}")
toolset_list = ", ".join([f"{ts}({p}%)" for ts, p in dist["toolsets"].items()])
print(f" Toolsets: {toolset_list}")
# Demo sampling
print("\n\n🎲 Sampling Examples:")
print("-" * 40)
test_distributions = ["image_gen", "research", "balanced", "default"]
for dist_name in test_distributions:
print(f"\n{dist_name}:")
# Sample 5 times to show variability
samples = []
for _ in range(5):
sampled = sample_toolsets_from_distribution(dist_name)
samples.append(sorted(sampled))
print(f" Sample 1: {samples[0]}")
print(f" Sample 2: {samples[1]}")
print(f" Sample 3: {samples[2]}")
print(f" Sample 4: {samples[3]}")
print(f" Sample 5: {samples[4]}")
# Show detailed info
print("\n\n📊 Detailed Distribution Info:")
print("-" * 40)
print_distribution_info("image_gen")
print_distribution_info("research")
+65 -148
View File
@@ -3,45 +3,29 @@
from typing import Dict, List, Any, Set, Optional, Tuple
# Shared tool list for CLI and all messaging platform toolsets.
# Edit this once to update all platforms simultaneously.
# Shared tool list for CLI and all messaging platform toolsets (edit once, all
# platforms follow). Desktop GUI affordances are deliberately NOT here: they live
# in `desktop_ui`/`project`, enabled per desktop-sourced session by the GUI gateway
# (tui_gateway/server.py::_load_enabled_toolsets). HA, kanban and computer_use
# entries are further gated by their tools' check_fns.
_HERMES_CORE_TOOLS = [
# Web
"web_search", "web_extract",
# Terminal + process management
"terminal", "process_manage",
# Desktop GUI affordances (read_terminal, open_preview, project_*) are NOT
# here: they live in `desktop_ui`/`project`, enabled only by the GUI gateway
# per desktop-sourced session (tui_gateway/server.py::_load_enabled_toolsets).
# File manipulation
"read_file", "write_file", "patch", "search_files",
# Vision + image generation
"vision_analyze", "image_generate",
# Skills
"skills_list", "skill_view", "skill_manage",
# Browser automation
"browser_navigate", "browser_snapshot", "browser_click",
"browser_type", "browser_scroll", "browser_back",
"browser_press", "browser_get_images",
"browser_vision", "browser_console", "browser_cdp", "browser_dialog",
# replaces other tools when browser.backend is "browser-use"
"browser_exec",
# Text-to-speech
"browser_exec", # replaces the other browser tools when browser.backend is "browser-use"
"text_to_speech",
# Planning & memory
"todo_list", "memory",
# Session history search
"session_search",
# Clarifying questions
"clarify",
# Code execution + delegation
"execute_code", "delegate_task",
# Cronjob management
"cronjob_manage",
# Home Assistant smart home control (gated on HASS_TOKEN via check_fn)
"ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service",
# Kanban coordination — check_fn in tools/kanban_tools.py admits these only
# for kanban workers (HERMES_KANBAN_TASK) or profiles enabling `kanban`.
"kanban_show", "kanban_list",
"kanban_complete", "kanban_block", "kanban_request_review",
"kanban_request_changes",
@@ -49,17 +33,17 @@ _HERMES_CORE_TOOLS = [
"kanban_comment", "kanban_create", "kanban_link",
"kanban_unblock",
"kanban_attach", "kanban_attach_url", "kanban_attachments",
# Computer use (macOS, gated on cua-driver being installed via check_fn)
"computer_use",
]
# Webhook payloads are untrusted third-party content: no file/system execution.
_HERMES_WEBHOOK_SAFE_TOOLS = [
"web_search",
"web_extract",
"vision_analyze",
"clarify",
_HERMES_WEBHOOK_SAFE_TOOLS = ["web_search", "web_extract", "vision_analyze", "clarify"]
_HA_TOOLS = ["ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service"]
_FEISHU_TOOLS = [
"feishu_doc_read", "feishu_drive_list_comments", "feishu_drive_list_comment_replies",
"feishu_drive_reply_comment", "feishu_drive_add_comment",
]
_YUANBAO_TOOLS = ["yb_query_group_info", "yb_query_group_members", "yb_send_dm", "yb_search_sticker", "yb_send_sticker"]
def _ts(description, tools=(), includes=(), **extra):
@@ -79,11 +63,7 @@ def _core_without(*excluded, kanban=True):
# Coding posture: everything you reach for while pairing on code; drops messaging,
# tts, image_gen, home-assistant, cron, kanban and computer-use.
_CODING_TOOLS = _core_without(
"image_generate", "text_to_speech", "cronjob_manage", "computer_use",
"ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service",
kanban=False,
)
_CODING_TOOLS = _core_without("image_generate", "text_to_speech", "cronjob_manage", "computer_use", *_HA_TOOLS, kanban=False)
# Core toolset definitions: individual tools or references to other toolsets.
TOOLSETS = {
@@ -129,12 +109,7 @@ TOOLSETS = {
"browser": _ts(
"Browser automation for web interaction (navigate, click, type, scroll, "
"iframes, hold-click) with web search for finding URLs",
[
"browser_navigate", "browser_snapshot", "browser_click", "browser_type",
"browser_scroll", "browser_back", "browser_press", "browser_get_images",
"browser_vision", "browser_console", "browser_cdp", "browser_dialog",
"browser_exec", "web_search",
],
[t for t in _HERMES_CORE_TOOLS if t.startswith("browser_")] + ["web_search"],
),
"cronjob": _ts(
"Cronjob management tool - create, list, update, pause, resume, remove, and "
@@ -178,10 +153,7 @@ TOOLSETS = {
["execute_code"],
),
"delegation": _ts("Spawn subagents with isolated context for complex subtasks", ["delegate_task"]),
"homeassistant": _ts(
"Home Assistant smart home control and monitoring",
["ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service"],
),
"homeassistant": _ts("Home Assistant smart home control and monitoring", _HA_TOOLS),
"kanban": _ts(
"Kanban multi-agent coordination — only active when the agent is spawned by "
"the kanban dispatcher (HERMES_KANBAN_TASK env set). The dispatcher runs "
@@ -190,12 +162,7 @@ TOOLSETS = {
"first-class review (request_review — not a block), return review changes, "
"block for human input, heartbeat during long ops, comment on threads, attach "
"files, and (for orchestrators) list, unblock, and fan out tasks.",
[
"kanban_show", "kanban_list", "kanban_complete", "kanban_block",
"kanban_request_review", "kanban_request_changes", "kanban_heartbeat",
"kanban_comment", "kanban_create", "kanban_link", "kanban_unblock",
"kanban_attach", "kanban_attach_url", "kanban_attachments",
],
[t for t in _HERMES_CORE_TOOLS if t.startswith("kanban_")],
),
"discord": _ts(
"Discord read and participate tools (fetch messages, search members, create threads)",
@@ -205,21 +172,9 @@ TOOLSETS = {
"Discord server management (list channels/roles, pin messages, assign roles)",
["discord_admin"],
),
"yuanbao": _ts(
"Yuanbao platform tools - group info, member queries, DM, stickers",
[
"yb_query_group_info", "yb_query_group_members", "yb_send_dm", "yb_search_sticker",
"yb_send_sticker",
],
),
"yuanbao": _ts("Yuanbao platform tools - group info, member queries, DM, stickers", _YUANBAO_TOOLS),
"feishu_doc": _ts("Read Feishu/Lark document content", ["feishu_doc_read"]),
"feishu_drive": _ts(
"Feishu/Lark document comment operations (list, reply, add)",
[
"feishu_drive_list_comments", "feishu_drive_list_comment_replies",
"feishu_drive_reply_comment", "feishu_drive_add_comment",
],
),
"feishu_drive": _ts("Feishu/Lark document comment operations (list, reply, add)", _FEISHU_TOOLS[1:]),
"spotify": _ts(
"Native Spotify playback, search, playlist, album, and library tools",
[
@@ -286,14 +241,7 @@ TOOLSETS = {
"hermes-mattermost": _bundle("Mattermost bot toolset - self-hosted team messaging (full access)"),
"hermes-matrix": _bundle("Matrix bot toolset - decentralized encrypted messaging (full access)"),
"hermes-dingtalk": _bundle("DingTalk bot toolset - enterprise messaging platform (full access)"),
"hermes-feishu": _bundle(
"Feishu/Lark bot toolset - enterprise messaging via Feishu/Lark (full access)",
[
"feishu_doc_read", "feishu_drive_list_comments",
"feishu_drive_list_comment_replies", "feishu_drive_reply_comment",
"feishu_drive_add_comment",
],
),
"hermes-feishu": _bundle("Feishu/Lark bot toolset - enterprise messaging via Feishu/Lark (full access)", _FEISHU_TOOLS),
"hermes-weixin": _bundle("Weixin bot toolset - personal WeChat messaging via iLink (full access)"),
"hermes-qqbot": _bundle("QQBot toolset - QQ messaging via Official Bot API v2 (full access)"),
"hermes-wecom": _bundle("WeCom bot toolset - enterprise WeChat messaging (full access)"),
@@ -302,10 +250,7 @@ TOOLSETS = {
),
"hermes-yuanbao": {
"description": "Yuanbao Bot 元宝消息平台工具集 - 群信息、成员查询、私聊、贴纸表情",
"tools": _HERMES_CORE_TOOLS + [
"yb_query_group_info", "yb_query_group_members", "yb_send_dm",
"yb_search_sticker", "yb_send_sticker",
],
"tools": _HERMES_CORE_TOOLS + _YUANBAO_TOOLS,
"module": "tools.yuanbao_tools",
"includes": [],
},
@@ -337,30 +282,33 @@ def _registry():
return None
def _registry_call(method: str, default):
"""registry.<method>() or *default* when the registry is unavailable or the call fails."""
registry = _registry()
if registry is None:
return default
try:
return getattr(registry, method)()
except Exception:
return default
def _registry_generation() -> Tuple[int, int]:
reg = _registry()
return (id(reg), getattr(reg, "_generation", 0)) if reg is not None else (0, 0)
def _static_copy(toolset: Dict[str, Any]) -> Dict[str, Any]:
return {
**toolset,
"tools": list(toolset.get("tools", [])),
"includes": list(toolset.get("includes", [])),
}
def get_toolset(name: str, *, include_registry: bool = True) -> Optional[Dict[str, Any]]:
"""Return a toolset definition, or None if unknown.
"""Toolset definition, or None if unknown.
include_registry=True merges tools plugins/overlays registered into this
toolset and resolves registry-only (plugin/MCP) toolsets and aliases.
include_registry=False returns only the static TOOLSETS entry (copied), so
platform reverse-mapping (#49622) is unaffected by registry additions.
include_registry=True merges plugin/overlay tools registered into this toolset
and resolves registry-only (plugin/MCP) toolsets and aliases; False returns a
copy of the static TOOLSETS entry only, so platform reverse-mapping is
unaffected by registry additions.
"""
toolset = TOOLSETS.get(name)
if not include_registry:
return _static_copy(toolset) if toolset else None
return {**toolset, "tools": list(toolset.get("tools", [])), "includes": list(toolset.get("includes", []))} if toolset else None
registry = _registry()
if registry is None:
@@ -391,24 +339,24 @@ def get_toolset(name: str, *, include_registry: bool = True) -> Optional[Dict[st
def bundle_non_core_tools(toolset_name: str) -> Set[str]:
"""A bundle's tools minus _HERMES_CORE_TOOLS (one level of includes).
Bundles are `_HERMES_CORE_TOOLS + extras`; disabling one must not strip the
core tools every other toolset shares. One `includes` pass suffices because
only hermes-gateway nests bundles. Unknown names: full resolution minus core.
Disabling a `core + extras` bundle must not strip the core tools every other
toolset shares. One `includes` pass suffices (only hermes-gateway nests
bundles). Unknown names: full resolution minus core.
"""
core = set(_HERMES_CORE_TOOLS)
ts_def = get_toolset(toolset_name)
if not (ts_def and "tools" in ts_def):
return set(resolve_toolset(toolset_name)) - core
to_remove = set(ts_def["tools"]) - core
to_remove = set(ts_def["tools"])
for inc in ts_def.get("includes", []):
inc_def = get_toolset(inc)
if inc_def and "tools" in inc_def:
to_remove.update(set(inc_def["tools"]) - core)
return to_remove
to_remove.update(inc_def["tools"])
return to_remove - core
# Memo keyed on (name, include_registry, id(registry), registry generation).
# Engages only at the public entry (visited is None); recursion is untouched.
# Memo keyed on (name, include_registry, id(registry), registry generation);
# engages only at the public entry (visited is None).
_resolve_toolset_memo: Dict[Tuple[str, bool, int, int], List[str]] = {}
@@ -422,23 +370,19 @@ def _plugin_platform_bundle(name: str) -> List[str]:
from gateway.platform_registry import platform_registry
if not platform_registry.is_registered(platform_name):
return []
tools = set(_HERMES_CORE_TOOLS)
registry = _registry()
if registry is not None:
try:
tools.update(e.name for e in registry.get_all_entries() if e.toolset == platform_name)
except Exception:
pass
return list(tools)
except Exception:
return []
tools = set(_HERMES_CORE_TOOLS)
try:
tools.update(e.name for e in _registry_call("get_all_entries", ()) if e.toolset == platform_name)
except Exception:
pass
return list(tools)
def resolve_toolset(name: str, visited: Set[str] = None, *, include_registry: bool = True) -> List[str]:
"""Recursively resolve a toolset (and its includes) to a sorted tool-name list.
include_registry=False resolves the static TOOLSETS view only (#49622).
"""
include_registry=False resolves the static TOOLSETS view only."""
external_call = visited is None
if external_call:
memo_key = (name, include_registry, *_registry_generation())
@@ -454,8 +398,7 @@ def resolve_toolset(name: str, visited: Set[str] = None, *, include_registry: bo
all_tools.update(resolve_toolset(toolset_name, visited.copy(), include_registry=include_registry))
return sorted(all_tools)
# Diamond include or cycle: return [] silently — the tools were (or will
# be) collected via another path, so this is not an error.
# Diamond include or cycle: [] silently — the tools are collected via another path.
if name in visited:
return []
visited.add(name)
@@ -470,32 +413,19 @@ def resolve_toolset(name: str, visited: Set[str] = None, *, include_registry: bo
result = sorted(tools)
if external_call:
# Stale-generation entries are never hit again; bound the memo.
if len(_resolve_toolset_memo) >= 256:
if len(_resolve_toolset_memo) >= 256: # stale-generation entries are never hit again
_resolve_toolset_memo.clear()
_resolve_toolset_memo[(name, include_registry, *_registry_generation())] = list(result)
_resolve_toolset_memo[memo_key] = list(result)
return result
def _get_plugin_toolset_names() -> Set[str]:
"""Registry toolset names absent from the static TOOLSETS dict."""
registry = _registry()
if registry is None:
return set()
try:
return {n for n in registry.get_registered_toolset_names() if n not in TOOLSETS}
except Exception:
return set()
return {n for n in _registry_call("get_registered_toolset_names", ()) if n not in TOOLSETS}
def _get_registry_toolset_aliases() -> Dict[str, str]:
registry = _registry()
if registry is None:
return {}
try:
return registry.get_registered_toolset_aliases()
except Exception:
return {}
return _registry_call("get_registered_toolset_aliases", {})
def _display_alias(ts_name: str, aliases: Dict[str, str]) -> Optional[str]:
@@ -527,23 +457,13 @@ def get_toolset_names() -> List[str]:
def validate_toolset(name: str) -> bool:
if name in {"all", "*"} or name in TOOLSETS:
return True
return name in _get_plugin_toolset_names() or name in _get_registry_toolset_aliases()
return (name in {"all", "*"} or name in TOOLSETS
or name in _get_plugin_toolset_names() or name in _get_registry_toolset_aliases())
def create_custom_toolset(
name: str,
description: str,
tools: List[str] = None,
includes: List[str] = None
) -> None:
def create_custom_toolset(name: str, description: str, tools: List[str] = None, includes: List[str] = None) -> None:
"""Register a runtime toolset in TOOLSETS."""
TOOLSETS[name] = {
"description": description,
"tools": tools or [],
"includes": includes or []
}
TOOLSETS[name] = _ts(description, tools or [], includes or [])
def get_toolset_info(name: str) -> Dict[str, Any]:
@@ -553,11 +473,8 @@ def get_toolset_info(name: str) -> Dict[str, Any]:
return None
resolved_tools = resolve_toolset(name)
return {
"name": name,
"description": toolset["description"],
"direct_tools": toolset["tools"],
"includes": toolset["includes"],
"resolved_tools": resolved_tools,
"tool_count": len(resolved_tools),
"is_composite": bool(toolset["includes"])
"name": name, "description": toolset["description"],
"direct_tools": toolset["tools"], "includes": toolset["includes"],
"resolved_tools": resolved_tools, "tool_count": len(resolved_tools),
"is_composite": bool(toolset["includes"]),
}