Skip to content

Commit ca89b9a

Browse files
refactor: inline single-caller helpers in plugin source resolution
Collapse _is_remote_source, _fetch_and_load_remote_skills, _fallback_skill_lookup, and _try_load_skill_file into their only call sites (_resolve_plugin_skills and load_public_skills). Keep _load_marketplace_object (two callers) and _load_skills_from_resolved_path (two code paths) as the only extracted helpers. Remove unit tests that targeted the deleted private functions; the integration-level tests already cover the same behavior through load_public_skills. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent c9b83ae commit ca89b9a

File tree

2 files changed

+60
-269
lines changed

2 files changed

+60
-269
lines changed

openhands-sdk/openhands/sdk/context/skills/skill.py

Lines changed: 59 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -962,11 +962,6 @@ def _load_marketplace_object(
962962
return None
963963

964964

965-
def _is_remote_source(source: str) -> bool:
966-
"""Check if a resolved source string is remote (needs fetching)."""
967-
return source.startswith(("github:", "https://", "http://", "git@", "git://"))
968-
969-
970965
def _load_skills_from_resolved_path(
971966
plugin_dir: Path,
972967
skill_base_dir: Path | None = None,
@@ -975,25 +970,16 @@ def _load_skills_from_resolved_path(
975970
976971
Tries to load as a full plugin first (has skills/ subdir), then
977972
falls back to loading as a single skill directory (has SKILL.md).
978-
979-
Args:
980-
plugin_dir: Path to the resolved plugin or skill directory.
981-
skill_base_dir: Base directory for computing relative skill source paths.
982-
983-
Returns:
984-
List of loaded Skill objects (may be empty).
985973
"""
986974
if not plugin_dir.is_dir():
987975
return []
988976

989-
# If it has a skills/ subdirectory, load skills from it (plugin structure)
990977
inner_skills_dir = plugin_dir / "skills"
991978
if inner_skills_dir.is_dir():
992979
from openhands.sdk.plugin.plugin import _load_skills
993980

994981
return _load_skills(plugin_dir)
995982

996-
# If the directory itself contains a SKILL.md, load as a single skill
997983
md_path = find_skill_md(plugin_dir)
998984
if md_path:
999985
try:
@@ -1018,21 +1004,17 @@ def _resolve_plugin_skills(
10181004
"""Resolve a marketplace plugin entry's source and load its skills.
10191005
10201006
Resolution order:
1021-
1. Resolve the source field via Marketplace.resolve_plugin_source()
1022-
2. For remote sources: fetch the plugin repo, then load skills from it
1023-
3. For local sources that exist: load skills directly
1024-
4. Fall back to name-based lookup in skills_dir (backward compatibility)
1025-
1026-
Args:
1027-
marketplace: The Marketplace object (for source resolution).
1028-
plugin_entry: The plugin entry to resolve.
1029-
skills_dir: The skills/ directory in the extensions repo.
1030-
repo_path: Root path of the extensions repo.
1031-
1032-
Returns:
1033-
List of Skill objects loaded from the plugin.
1007+
1. Resolve source via Marketplace.resolve_plugin_source()
1008+
2. Remote sources: fetch via Plugin.fetch(), then load skills
1009+
3. Local sources: load skills directly from the path
1010+
4. Fallback: name-based lookup in skills_dir (backward compatibility)
10341011
"""
1012+
from openhands.sdk.plugin import Plugin, PluginFetchError
1013+
10351014
# Try to resolve the source field
1015+
source: str | None = None
1016+
ref: str | None = None
1017+
subpath: str | None = None
10361018
try:
10371019
source, ref, subpath = marketplace.resolve_plugin_source(plugin_entry)
10381020
except Exception as e:
@@ -1041,113 +1023,63 @@ def _resolve_plugin_skills(
10411023
plugin_entry.name,
10421024
e,
10431025
)
1044-
return _fallback_skill_lookup(plugin_entry.name, skills_dir, repo_path)
10451026

10461027
# Remote source: fetch and load
1047-
if _is_remote_source(source):
1048-
return _fetch_and_load_remote_skills(source, ref, subpath, plugin_entry.name)
1049-
1050-
# Local source: try to load from the resolved path
1051-
local_path = Path(source)
1052-
if local_path.is_dir():
1053-
skills = _load_skills_from_resolved_path(local_path, skill_base_dir=repo_path)
1054-
if skills:
1055-
return skills
1056-
1057-
# Fall back to name-based lookup in skills/ directory
1058-
return _fallback_skill_lookup(plugin_entry.name, skills_dir, repo_path)
1059-
1060-
1061-
def _fetch_and_load_remote_skills(
1062-
source: str,
1063-
ref: str | None,
1064-
subpath: str | None,
1065-
plugin_name: str,
1066-
) -> list[Skill]:
1067-
"""Fetch a remote plugin and load skills from it.
1068-
1069-
Args:
1070-
source: Remote source string (e.g. "github:owner/repo").
1071-
ref: Optional git ref (branch, tag, commit).
1072-
subpath: Optional subdirectory within the fetched repo.
1073-
plugin_name: Plugin name (for logging).
1074-
1075-
Returns:
1076-
List of loaded Skill objects.
1077-
"""
1078-
from openhands.sdk.plugin import Plugin, PluginFetchError
1079-
1080-
try:
1081-
plugin_path = Plugin.fetch(source=source, ref=ref, repo_path=subpath)
1082-
except PluginFetchError as e:
1083-
logger.warning(
1084-
"Failed to fetch plugin '%s' from %s: %s",
1085-
plugin_name,
1086-
source,
1087-
e,
1088-
)
1089-
return []
1090-
1091-
# Try loading as a full plugin first
1092-
try:
1093-
plugin = Plugin.load(plugin_path)
1094-
skills = plugin.get_all_skills()
1095-
if skills:
1096-
logger.debug(
1097-
"Loaded %d skills from plugin '%s' (%s)",
1098-
len(skills),
1099-
plugin_name,
1028+
if source and source.startswith(
1029+
("github:", "https://", "http://", "git@", "git://")
1030+
):
1031+
try:
1032+
plugin_path = Plugin.fetch(source=source, ref=ref, repo_path=subpath)
1033+
except PluginFetchError as e:
1034+
logger.warning(
1035+
"Failed to fetch plugin '%s' from %s: %s",
1036+
plugin_entry.name,
11001037
source,
1038+
e,
11011039
)
1102-
return skills
1103-
except Exception:
1104-
pass
1105-
1106-
# Fall back to loading skills directly from the directory
1107-
return _load_skills_from_resolved_path(plugin_path)
1040+
return []
11081041

1042+
# Try loading as a full plugin, fall back to raw directory
1043+
try:
1044+
plugin = Plugin.load(plugin_path)
1045+
skills = plugin.get_all_skills()
1046+
if skills:
1047+
return skills
1048+
except Exception:
1049+
pass
1050+
return _load_skills_from_resolved_path(plugin_path)
11091051

1110-
def _fallback_skill_lookup(
1111-
skill_name: str, skills_dir: Path, repo_path: Path
1112-
) -> list[Skill]:
1113-
"""Look up a skill by name in the skills directory (backward compat).
1114-
1115-
Checks for SKILL.md in a subdirectory, then legacy .md files.
1116-
1117-
Args:
1118-
skill_name: Name of the skill to look up.
1119-
skills_dir: The skills/ directory to search in.
1120-
repo_path: Root path of the repo (for Skill.load base_dir).
1121-
1122-
Returns:
1123-
List containing the loaded Skill, or empty list if not found.
1124-
"""
1125-
skill_md = skills_dir / skill_name / "SKILL.md"
1126-
if skill_md.exists():
1127-
return _try_load_skill_file(skill_md, repo_path)
1052+
# Local source: try to load from the resolved path
1053+
if source:
1054+
local_path = Path(source)
1055+
if local_path.is_dir():
1056+
skills = _load_skills_from_resolved_path(
1057+
local_path, skill_base_dir=repo_path
1058+
)
1059+
if skills:
1060+
return skills
11281061

1129-
legacy_md = skills_dir / f"{skill_name}.md"
1130-
if legacy_md.exists():
1131-
return _try_load_skill_file(legacy_md, repo_path)
1062+
# Fall back to name-based lookup in skills/ directory
1063+
for candidate in (
1064+
skills_dir / plugin_entry.name / "SKILL.md",
1065+
skills_dir / f"{plugin_entry.name}.md",
1066+
):
1067+
if candidate.exists():
1068+
try:
1069+
skill = Skill.load(path=candidate, skill_base_dir=repo_path)
1070+
if skill is not None:
1071+
return [skill]
1072+
except Exception as e:
1073+
logger.warning(f"Failed to load skill from {candidate.name}: {e}")
1074+
return []
11321075

11331076
logger.debug(
11341077
"Skill '%s' not found in skills dir via fallback lookup",
1135-
skill_name,
1078+
plugin_entry.name,
11361079
)
11371080
return []
11381081

11391082

1140-
def _try_load_skill_file(skill_file: Path, repo_path: Path) -> list[Skill]:
1141-
"""Try to load a single skill file, returning it in a list or empty."""
1142-
try:
1143-
skill = Skill.load(path=skill_file, skill_base_dir=repo_path)
1144-
if skill is not None:
1145-
return [skill]
1146-
except Exception as e:
1147-
logger.warning(f"Failed to load skill from {skill_file.name}: {e}")
1148-
return []
1149-
1150-
11511083
def load_public_skills(
11521084
repo_url: str = PUBLIC_SKILLS_REPO,
11531085
branch: str = PUBLIC_SKILLS_BRANCH,
@@ -1250,10 +1182,13 @@ def load_public_skills(
12501182
)
12511183

12521184
for skill_file in all_skill_files:
1253-
loaded = _try_load_skill_file(skill_file, repo_path)
1254-
for skill in loaded:
1255-
all_skills.append(skill)
1256-
logger.debug(f"Loaded public skill: {skill.name}")
1185+
try:
1186+
skill = Skill.load(path=skill_file, skill_base_dir=repo_path)
1187+
if skill is not None:
1188+
all_skills.append(skill)
1189+
logger.debug(f"Loaded public skill: {skill.name}")
1190+
except Exception as e:
1191+
logger.warning(f"Failed to load skill from {skill_file.name}: {e}")
12571192

12581193
except Exception as e:
12591194
logger.warning(f"Failed to load public skills from {repo_url}: {e!s}")

0 commit comments

Comments
 (0)