This commit is contained in:
2026-05-04 15:58:24 -07:00
parent bca85defa4
commit 3ce339b3c1
6 changed files with 322 additions and 18 deletions
+131 -2
View File
@@ -530,6 +530,53 @@ def get_plugin_class(name: str) -> Optional[Type[Provider]]:
return info.plugin_class
def get_plugin_capabilities(
name: str,
config: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Return a normalized capability summary for a plugin name."""
info = REGISTRY.get(name)
if info is None:
return {
"name": str(name or "").strip().lower(),
"supported_cmdlets": [],
"supports_search": False,
"supports_upload": False,
"supports_pipe_download": False,
"supports_delete_file": False,
"is_multi_instance": False,
"configured_instances": [],
}
supported_cmdlets = sorted(str(c) for c in info.supported_cmdlets)
supports_pipe_download = _class_supports_method(
info.plugin_class,
"resolve_pipe_result_download",
Provider.resolve_pipe_result_download,
)
delete_method = getattr(info.plugin_class, "delete_file", None)
base_delete_method = getattr(Provider, "delete_file", None)
supports_delete_file = callable(delete_method) and delete_method is not base_delete_method
configured_instances: List[str] = []
try:
plugin_obj = info.plugin_class(config or {})
configured_instances = [str(v) for v in (plugin_obj.configured_instances() or []) if str(v).strip()]
except Exception:
configured_instances = []
return {
"name": info.canonical_name,
"supported_cmdlets": supported_cmdlets,
"supports_search": bool(info.supports_search),
"supports_upload": bool(info.supports_upload),
"supports_pipe_download": bool(supports_pipe_download),
"supports_delete_file": bool(supports_delete_file),
"is_multi_instance": bool(info.is_multi_instance),
"configured_instances": configured_instances,
}
def selection_auto_stage_for_table(
table_type: str,
stage_args: Optional[Sequence[str]] = None,
@@ -565,12 +612,30 @@ def _supports_upload(provider: Provider) -> bool:
return exposed and _class_supports_method(provider.__class__, "upload", Provider.upload)
def _supports_pipe_result_download(provider: Provider) -> bool:
return _class_supports_method(
provider.__class__,
"resolve_pipe_result_download",
Provider.resolve_pipe_result_download,
)
def _supports_delete_file(provider: Provider) -> bool:
method = getattr(provider.__class__, "delete_file", None)
base_method = getattr(Provider, "delete_file", None)
return callable(method) and method is not base_method
def _supports_capability(provider: Provider, capability: str) -> bool:
capability_key = str(capability or "").strip().lower()
if capability_key == "search":
return _supports_search(provider)
if capability_key in {"upload", "file", "file-provider"}:
return _supports_upload(provider)
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
return _supports_pipe_result_download(provider)
if capability_key in {"delete-file", "delete_file", "delete"}:
return _supports_delete_file(provider)
if capability_key in {"pipe-item-context", "pipe-context"}:
return _class_supports_method(
provider.__class__,
@@ -592,6 +657,16 @@ def _info_supports_capability(info: PluginInfo, capability: str) -> bool:
return bool(info.supports_search)
if capability_key in {"upload", "file", "file-provider"}:
return bool(info.supports_upload)
if capability_key in {"pipe-download", "pipe_result_download", "pipe-result-download"}:
return _class_supports_method(
info.plugin_class,
"resolve_pipe_result_download",
Provider.resolve_pipe_result_download,
)
if capability_key in {"delete-file", "delete_file", "delete"}:
method = getattr(info.plugin_class, "delete_file", None)
base_method = getattr(Provider, "delete_file", None)
return callable(method) and method is not base_method
if capability_key in {"pipe-item-context", "pipe-context"}:
return _class_supports_method(
info.plugin_class,
@@ -762,8 +837,11 @@ def list_configured_plugin_names_with_capability(
name = info.canonical_name
if info.is_multi_instance:
try:
instances = info.plugin_class(cfg).configured_instances()
if instances:
plugin_obj = info.plugin_class(cfg)
instances = plugin_obj.configured_instances()
# Treat explicit multi-instance names as configured, but also allow
# a default/single config block for multi-instance plugins.
if instances or bool(plugin_obj.plugin_config_root()):
result.append(name)
except Exception:
pass
@@ -774,6 +852,55 @@ def list_configured_plugin_names_with_capability(
return sorted(result)
def list_plugin_names_for_cmdlet(
cmdlet_name: str,
config: Optional[Dict[str, Any]] = None,
*,
configured_only: bool = False,
) -> List[str]:
"""Return plugin names suitable for a cmdlet.
Priority:
1) Plugins that explicitly declare the cmdlet in SUPPORTED_CMDLETS.
2) Capability fallback for legacy plugins that do not yet declare cmdlets.
"""
cmd = str(cmdlet_name or "").strip().lower()
if not cmd:
return []
supported = {
info.canonical_name for info in REGISTRY.get_plugins_for_cmdlet(cmd)
}
fallback_capability = {
"search-file": "search",
"add-file": "upload",
"download-file": "search",
"delete-file": "delete-file",
}.get(cmd)
if fallback_capability:
supported.update(list_plugin_names_with_capability(fallback_capability))
if not configured_only:
return sorted(supported)
cfg = config or {}
configured: set[str] = set()
if fallback_capability:
configured.update(list_configured_plugin_names_with_capability(fallback_capability, cfg))
# Keep cmdlet-declared plugins if they appear configured in plugin/provider sections.
plugin_section: Dict[str, Any] = cfg.get("plugin") or {} # type: ignore[assignment]
provider_section: Dict[str, Any] = cfg.get("provider") or {} # type: ignore[assignment]
for name in supported:
key = str(name or "").strip().lower()
if isinstance(plugin_section.get(key), dict) or isinstance(provider_section.get(key), dict):
configured.add(name)
return sorted(configured)
def match_plugin_name_for_url(url: str) -> Optional[str]:
raw_url = str(url or "").strip()
raw_url_lower = raw_url.lower()
@@ -990,11 +1117,13 @@ __all__ = [
"get_plugin_with_capability",
"list_plugins_with_capability",
"list_plugin_names_with_capability",
"list_plugin_names_for_cmdlet",
"list_configured_plugin_names_with_capability",
"match_plugin_name_for_url",
"get_plugin_for_url",
"list_selection_url_prefixes",
"get_plugin_class",
"get_plugin_capabilities",
"selection_auto_stage_for_table",
"plugin_inline_query_choices",
"is_known_plugin_name",