Source code for design_research_agents._skills._config

"""Public-facing configuration for Agent Skills support."""

from __future__ import annotations

import os
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Literal


[docs] @dataclass(slots=True, frozen=True, kw_only=True) class SkillsConfig: """Immutable configuration for Agent Skills discovery and prompt wiring.""" project_root: str | os.PathLike[str] = "." """Root used to resolve the default ``.agents/skills`` directory and relative extra paths.""" extra_paths: tuple[str, ...] = () """Additional skill-root directories searched after the project-local skills root.""" pinned_skills: tuple[str, ...] = () """Skill names preloaded into prompts for deterministic constructor-scoped behavior.""" catalog_prompt_target: Literal["system", "user"] = "system" """Prompt location used for the discoverable skills catalog when automatic activation is enabled.""" allow_automatic_activation: bool = True """Whether tool-capable flows should advertise and expose ``skills.activate``.""" def __post_init__(self) -> None: """Normalize string/path inputs into stable immutable tuples.""" normalized_project_root = os.fspath(self.project_root) normalized_extra_paths = _normalize_path_sequence( raw_value=self.extra_paths, field_name="extra_paths", ) normalized_pinned_skills = _normalize_string_sequence( raw_value=self.pinned_skills, field_name="pinned_skills", ) if not isinstance(self.catalog_prompt_target, str): raise TypeError("catalog_prompt_target must be a string.") normalized_catalog_prompt_target = self.catalog_prompt_target.strip().lower() if normalized_catalog_prompt_target not in {"system", "user"}: raise ValueError("catalog_prompt_target must be either 'system' or 'user'.") if not isinstance(self.allow_automatic_activation, bool): raise TypeError("allow_automatic_activation must be a boolean.") object.__setattr__(self, "project_root", normalized_project_root) object.__setattr__(self, "extra_paths", normalized_extra_paths) object.__setattr__(self, "pinned_skills", normalized_pinned_skills) object.__setattr__(self, "catalog_prompt_target", normalized_catalog_prompt_target)
def _normalize_path_sequence(*, raw_value: object, field_name: str) -> tuple[str, ...]: """Normalize tuple-like path configuration while rejecting scalar strings.""" if raw_value is None: return () if isinstance(raw_value, (str, os.PathLike)): raise TypeError(f"{field_name} must be a sequence of paths, not a single string/path.") if not isinstance(raw_value, Sequence): raise TypeError(f"{field_name} must be a sequence of paths.") return tuple(normalized_path for item in raw_value if (normalized_path := os.fspath(item).strip())) def _normalize_string_sequence(*, raw_value: object, field_name: str) -> tuple[str, ...]: """Normalize tuple-like string configuration while rejecting scalar strings.""" if raw_value is None: return () if isinstance(raw_value, str): raise TypeError(f"{field_name} must be a sequence of strings, not a single string.") if not isinstance(raw_value, Sequence): raise TypeError(f"{field_name} must be a sequence of strings.") normalized_names: list[str] = [] for item in raw_value: if not isinstance(item, str): raise TypeError(f"{field_name} may only contain strings.") normalized_name = item.strip() if normalized_name: normalized_names.append(normalized_name) return tuple(normalized_names)