89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
|
|
|
||
|
|
"""External tool capability detection via dynamic plugin scanning."""
|
||
|
|
import os
|
||
|
|
import yaml
|
||
|
|
from typing import Dict, Any, List
|
||
|
|
|
||
|
|
|
||
|
|
def _get_active_plugins() -> List[str]:
|
||
|
|
"""Scan all plugin roots for active plugins."""
|
||
|
|
active_plugins = []
|
||
|
|
plugin_roots = ["/a0/plugins", "/a0/usr/plugins"]
|
||
|
|
|
||
|
|
for root in plugin_roots:
|
||
|
|
if not os.path.isdir(root):
|
||
|
|
continue
|
||
|
|
for plugin_name in os.listdir(root):
|
||
|
|
plugin_dir = os.path.join(root, plugin_name)
|
||
|
|
if not os.path.isdir(plugin_dir):
|
||
|
|
continue
|
||
|
|
|
||
|
|
plugin_yaml = os.path.join(plugin_dir, "plugin.yaml")
|
||
|
|
if not os.path.isfile(plugin_yaml):
|
||
|
|
continue
|
||
|
|
|
||
|
|
# Check if plugin is active
|
||
|
|
is_active = False
|
||
|
|
if os.path.isfile(os.path.join(plugin_dir, ".toggle-1")):
|
||
|
|
is_active = True
|
||
|
|
elif os.path.isfile(os.path.join(plugin_dir, ".toggle-0")):
|
||
|
|
is_active = False
|
||
|
|
else:
|
||
|
|
# No toggle files, check always_enabled
|
||
|
|
try:
|
||
|
|
with open(plugin_yaml, "r") as f:
|
||
|
|
config = yaml.safe_load(f)
|
||
|
|
if config and config.get("always_enabled", False):
|
||
|
|
is_active = True
|
||
|
|
except (yaml.YAMLError) as e:
|
||
|
|
pass
|
||
|
|
|
||
|
|
if is_active:
|
||
|
|
active_plugins.append(plugin_name)
|
||
|
|
|
||
|
|
return active_plugins
|
||
|
|
|
||
|
|
|
||
|
|
def generate_capability_report(config: Dict[str, Any] = None) -> Dict[str, Any]:
|
||
|
|
"""Generate a capability report for all actively registered tools."""
|
||
|
|
active_plugins = _get_active_plugins()
|
||
|
|
report = {}
|
||
|
|
|
||
|
|
for name in active_plugins:
|
||
|
|
report[name] = {
|
||
|
|
"available": True,
|
||
|
|
"fallback": "plugin is active",
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add a few standard tools not represented as plugins if they exist in config
|
||
|
|
if config:
|
||
|
|
tools_config = config.get("external_tools", {})
|
||
|
|
for name, cfg in tools_config.items():
|
||
|
|
if name not in report:
|
||
|
|
report[name] = {
|
||
|
|
"available": False,
|
||
|
|
"fallback": cfg.get("fallback", "not available"),
|
||
|
|
}
|
||
|
|
|
||
|
|
return report
|
||
|
|
|
||
|
|
|
||
|
|
def check_tool(tool_name: str) -> Dict[str, Any]:
|
||
|
|
"""Return availability for one plugin/tool capability name."""
|
||
|
|
active_plugins = _get_active_plugins()
|
||
|
|
if tool_name in active_plugins:
|
||
|
|
return {"available": True, "fallback": "plugin is active"}
|
||
|
|
# Also match by declared tool name inside active plugin manifests.
|
||
|
|
for plugin_name in active_plugins:
|
||
|
|
plugin_dir = os.path.join("/a0/usr/plugins", plugin_name)
|
||
|
|
plugin_yaml = os.path.join(plugin_dir, "plugin.yaml")
|
||
|
|
try:
|
||
|
|
with open(plugin_yaml, "r", encoding="utf-8") as f:
|
||
|
|
config = yaml.safe_load(f) or {}
|
||
|
|
for tool in config.get("tools", []) or []:
|
||
|
|
if tool.get("name") == tool_name:
|
||
|
|
return {"available": True, "fallback": f"declared by plugin {plugin_name}"}
|
||
|
|
except Exception:
|
||
|
|
continue
|
||
|
|
return {"available": False, "fallback": "not active or not declared"}
|