mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor backend module architecture
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Agent Skill 元数据、市场和本地生命周期能力。"""
|
||||
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
import re
|
||||
from typing import TypedDict
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 磁盘读取上限属于 Skill 文档格式约束,市场扫描和 Agent 加载必须共用。
|
||||
MAX_SKILL_FILE_SIZE = 1 * 1024 * 1024
|
||||
MAX_SKILL_NAME_LENGTH = 64
|
||||
MAX_SKILL_DESCRIPTION_LENGTH = 1024
|
||||
MAX_SKILL_COMPATIBILITY_LENGTH = 500
|
||||
|
||||
|
||||
class SkillMetadata(TypedDict):
|
||||
"""描述符合 Agent Skills 规范的已校验元数据。"""
|
||||
|
||||
path: str
|
||||
id: str
|
||||
name: str
|
||||
version: int
|
||||
description: str
|
||||
license: str | None
|
||||
compatibility: str | None
|
||||
metadata: dict[str, str]
|
||||
allowed_tools: list[str]
|
||||
|
||||
|
||||
def _validate_metadata(raw: object, skill_path: str) -> dict[str, str]:
|
||||
"""将 YAML metadata 字段规范化为字符串键值映射。"""
|
||||
if not isinstance(raw, dict):
|
||||
if raw:
|
||||
logger.warning(
|
||||
"Ignoring non-dict metadata in %s (got %s)",
|
||||
skill_path,
|
||||
type(raw).__name__,
|
||||
)
|
||||
return {}
|
||||
return {str(key): str(value) for key, value in raw.items()}
|
||||
|
||||
|
||||
def parse_skill_metadata( # noqa: C901
|
||||
content: str,
|
||||
skill_path: str,
|
||||
skill_id: str,
|
||||
) -> SkillMetadata | None:
|
||||
"""解析并校验一个 SKILL.md 的 YAML 前言。"""
|
||||
if len(content) > MAX_SKILL_FILE_SIZE:
|
||||
logger.warning(
|
||||
"Skipping %s: content too large (%d bytes)", skill_path, len(content)
|
||||
)
|
||||
return None
|
||||
|
||||
match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
|
||||
if not match:
|
||||
logger.warning("Skipping %s: no valid YAML frontmatter found", skill_path)
|
||||
return None
|
||||
|
||||
try:
|
||||
frontmatter_data = yaml.safe_load(match.group(1))
|
||||
except yaml.YAMLError as err:
|
||||
logger.warning("Invalid YAML in %s: %s", skill_path, err)
|
||||
return None
|
||||
if not isinstance(frontmatter_data, dict):
|
||||
logger.warning("Skipping %s: frontmatter is not a mapping", skill_path)
|
||||
return None
|
||||
|
||||
name = str(frontmatter_data.get("name", "")).strip()
|
||||
description = str(frontmatter_data.get("description", "")).strip()
|
||||
if not name or not description:
|
||||
logger.warning(
|
||||
"Skipping %s: missing required 'name' or 'description'", skill_path
|
||||
)
|
||||
return None
|
||||
if len(description) > MAX_SKILL_DESCRIPTION_LENGTH:
|
||||
logger.warning(
|
||||
"Description exceeds %d characters in %s, truncating",
|
||||
MAX_SKILL_DESCRIPTION_LENGTH,
|
||||
skill_path,
|
||||
)
|
||||
description = description[:MAX_SKILL_DESCRIPTION_LENGTH]
|
||||
|
||||
raw_tools = frontmatter_data.get("allowed-tools")
|
||||
if isinstance(raw_tools, str):
|
||||
allowed_tools = [
|
||||
tool.strip(",")
|
||||
for tool in raw_tools.split()
|
||||
if tool.strip(",")
|
||||
]
|
||||
else:
|
||||
if raw_tools is not None:
|
||||
logger.warning(
|
||||
"Ignoring non-string 'allowed-tools' in %s (got %s)",
|
||||
skill_path,
|
||||
type(raw_tools).__name__,
|
||||
)
|
||||
allowed_tools = []
|
||||
|
||||
compatibility = str(frontmatter_data.get("compatibility", "")).strip() or None
|
||||
if compatibility and len(compatibility) > MAX_SKILL_COMPATIBILITY_LENGTH:
|
||||
logger.warning(
|
||||
"Compatibility exceeds %d characters in %s, truncating",
|
||||
MAX_SKILL_COMPATIBILITY_LENGTH,
|
||||
skill_path,
|
||||
)
|
||||
compatibility = compatibility[:MAX_SKILL_COMPATIBILITY_LENGTH]
|
||||
|
||||
raw_version = frontmatter_data.get("version")
|
||||
version = 0
|
||||
if raw_version is not None:
|
||||
try:
|
||||
version = int(raw_version)
|
||||
except (ValueError, TypeError):
|
||||
logger.warning(
|
||||
"Invalid 'version' in %s (got %r), defaulting to 0",
|
||||
skill_path,
|
||||
raw_version,
|
||||
)
|
||||
|
||||
return SkillMetadata(
|
||||
id=skill_id,
|
||||
name=name,
|
||||
version=version,
|
||||
description=description,
|
||||
path=skill_path,
|
||||
metadata=_validate_metadata(frontmatter_data.get("metadata", {}), skill_path),
|
||||
license=str(frontmatter_data.get("license", "")).strip() or None,
|
||||
compatibility=compatibility,
|
||||
allowed_tools=allowed_tools,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user