mirror of
https://github.com/langgenius/dify.git
synced 2026-06-10 18:24:09 +08:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
124 lines
4.3 KiB
Python
124 lines
4.3 KiB
Python
"""Standardize an uploaded Skill into the agent drive (ENG-594).
|
|
|
|
A validated Skill package is normalized into two **drive-owned** objects committed
|
|
to the agent drive (Agent Files §5.4 / §4):
|
|
|
|
* ``<slug>/SKILL.md`` — the canonical entry, the source of truth for loading.
|
|
* ``<slug>/.DIFY-SKILL-FULL.zip`` — the full archive, kept only to restore the
|
|
complete skill contents.
|
|
|
|
Both are stored as ``ToolFile`` records and bound via ``AgentDriveService.commit``
|
|
with ``value_owned_by_drive=True`` (the drive owns their lifecycle). The returned
|
|
skill ref records the stable drive paths + file ids (not just the raw upload id),
|
|
so the Composer can reload the bound skill list.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from core.tools.tool_file_manager import ToolFileManager
|
|
from models.agent_config_entities import AgentSkillRefConfig
|
|
from services.agent.skill_package_service import SkillPackageService
|
|
from services.agent_drive_service import AgentDriveService, DriveCommitItem, DriveFileRef
|
|
|
|
_FULL_ARCHIVE_NAME = ".DIFY-SKILL-FULL.zip"
|
|
_SKILL_MD_NAME = "SKILL.md"
|
|
_SLUG_RE = re.compile(r"[^a-z0-9._-]+")
|
|
|
|
|
|
def slugify_skill_name(name: str) -> str:
|
|
slug = _SLUG_RE.sub("-", (name or "").strip().lower()).strip("-._")
|
|
return slug or "skill"
|
|
|
|
|
|
class SkillStandardizeService:
|
|
"""Validate + standardize a Skill package into a per-agent drive."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
package_service: SkillPackageService | None = None,
|
|
drive_service: AgentDriveService | None = None,
|
|
tool_file_manager: ToolFileManager | None = None,
|
|
) -> None:
|
|
self._package = package_service or SkillPackageService()
|
|
self._drive = drive_service or AgentDriveService()
|
|
self._tool_files = tool_file_manager or ToolFileManager()
|
|
|
|
def standardize(
|
|
self,
|
|
*,
|
|
content: bytes,
|
|
filename: str,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
agent_id: str,
|
|
) -> dict[str, Any]:
|
|
manifest = self._package.validate_and_extract(content=content, filename=filename)
|
|
skill_md_bytes = self._package.read_member_bytes(content=content, member_path=manifest.entry_path)
|
|
slug = slugify_skill_name(manifest.name)
|
|
|
|
# Two drive-owned ToolFiles: canonical SKILL.md + the full archive.
|
|
md_tool_file = self._tool_files.create_file_by_raw(
|
|
user_id=user_id,
|
|
tenant_id=tenant_id,
|
|
conversation_id=None,
|
|
file_binary=skill_md_bytes,
|
|
mimetype="text/markdown",
|
|
filename=_SKILL_MD_NAME,
|
|
)
|
|
archive_tool_file = self._tool_files.create_file_by_raw(
|
|
user_id=user_id,
|
|
tenant_id=tenant_id,
|
|
conversation_id=None,
|
|
file_binary=content,
|
|
mimetype="application/zip",
|
|
filename=_FULL_ARCHIVE_NAME,
|
|
)
|
|
|
|
skill_md_key = f"{slug}/{_SKILL_MD_NAME}"
|
|
archive_key = f"{slug}/{_FULL_ARCHIVE_NAME}"
|
|
self._drive.commit(
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
agent_id=agent_id,
|
|
items=[
|
|
DriveCommitItem(
|
|
key=skill_md_key,
|
|
file_ref=DriveFileRef(kind="tool_file", id=md_tool_file.id),
|
|
value_owned_by_drive=True,
|
|
),
|
|
DriveCommitItem(
|
|
key=archive_key,
|
|
file_ref=DriveFileRef(kind="tool_file", id=archive_tool_file.id),
|
|
value_owned_by_drive=True,
|
|
),
|
|
],
|
|
)
|
|
|
|
skill_ref = AgentSkillRefConfig.model_validate(
|
|
{
|
|
"id": manifest.hash,
|
|
"name": manifest.name,
|
|
"description": manifest.description,
|
|
"file_id": archive_tool_file.id,
|
|
"path": slug,
|
|
"size": manifest.size,
|
|
"hash": manifest.hash,
|
|
"entry_path": skill_md_key,
|
|
"skill_md_file_id": md_tool_file.id,
|
|
"skill_md_key": skill_md_key,
|
|
"full_archive_file_id": archive_tool_file.id,
|
|
"full_archive_key": archive_key,
|
|
}
|
|
)
|
|
return {
|
|
"skill": skill_ref.model_dump(exclude_none=True),
|
|
"manifest": manifest.model_dump(),
|
|
}
|
|
|
|
|
|
__all__ = ["SkillStandardizeService", "slugify_skill_name"]
|