vibe/vibe/core/skills/models.py
Pierre Rossinès 95336528f6
v2.7.6 (#600)
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Simon <80467011+sorgfresser@users.noreply.github.com>
Co-authored-by: angelapopopo <angele.lenglemetz@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-04-16 11:10:26 +02:00

98 lines
2.9 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any
from pydantic import BaseModel, Field, field_validator
class SkillMetadata(BaseModel):
model_config = {"populate_by_name": True}
name: str = Field(
...,
min_length=1,
max_length=64,
pattern=r"^[a-z0-9]+(-[a-z0-9]+)*$",
description="Skill identifier. Lowercase letters, numbers, and hyphens only.",
)
description: str = Field(
...,
min_length=1,
max_length=1024,
description="What this skill does and when to use it.",
)
license: str | None = Field(
default=None, description="License name or reference to a bundled license file."
)
compatibility: str | None = Field(
default=None,
max_length=500,
description="Environment requirements (intended product, system packages, etc.).",
)
metadata: dict[str, str] = Field(
default_factory=dict,
description="Arbitrary key-value mapping for additional metadata.",
)
allowed_tools: list[str] = Field(
default_factory=list,
validation_alias="allowed-tools",
description="Space-delimited list of pre-approved tools (experimental).",
)
user_invocable: bool = Field(
default=True,
validation_alias="user-invocable",
description="Controls whether the skill appears in the slash command menu.",
)
@field_validator("allowed_tools", mode="before")
@classmethod
def parse_allowed_tools(cls, v: str | list[str] | None) -> list[str]:
if v is None:
return []
if isinstance(v, str):
return v.split()
return list(v)
@field_validator("metadata", mode="before")
@classmethod
def normalize_metadata(cls, v: dict[str, Any] | None) -> dict[str, str]:
if v is None:
return {}
return {str(k): str(val) for k, val in v.items()}
class SkillInfo(BaseModel):
name: str
description: str
license: str | None = None
compatibility: str | None = None
metadata: dict[str, str] = Field(default_factory=dict)
allowed_tools: list[str] = Field(default_factory=list)
user_invocable: bool = True
skill_path: Path
model_config = {"arbitrary_types_allowed": True}
@property
def skill_dir(self) -> Path:
return self.skill_path.parent.resolve()
@classmethod
def from_metadata(cls, meta: SkillMetadata, skill_path: Path) -> SkillInfo:
return cls(
name=meta.name,
description=meta.description,
license=meta.license,
compatibility=meta.compatibility,
metadata=meta.metadata,
allowed_tools=meta.allowed_tools,
user_invocable=meta.user_invocable,
skill_path=skill_path.resolve(),
)
class ParsedSkillCommand(BaseModel):
name: str
content: str
extra_instructions: str | None = None