from collections.abc import Mapping from pydantic import Field from pydantic import field_validator from .common import DocsPath from .common import StrictFrozenModel from .common import frozen_mapping from .prompt import PromptArgumentEntry def _empty_docs_mapping() -> Mapping[DocsPath, str]: return frozen_mapping() class ReferenceRecord(StrictFrozenModel): """Registry record for a resolved skill reference document.""" ref_id: str uri: str relpath: DocsPath mime_type: str title: str | None content: str class SkillRecord(StrictFrozenModel): """Registry record containing a fully resolved skill and references.""" skill_id: str name: str description: str version: str tags: tuple[str, ...] capabilities: tuple[str, ...] document_uri: str document_relpath: DocsPath document_content: str references: Mapping[str, ReferenceRecord] = Field(default_factory=frozen_mapping) @field_validator("references", mode="before") @classmethod def freeze_references(cls, value: Mapping[str, ReferenceRecord] | None) -> Mapping[str, ReferenceRecord]: return frozen_mapping(value) class SkillSummaryRecord(StrictFrozenModel): """Compact skill summary exposed by catalog listing APIs.""" skill_id: str name: str description: str tags: tuple[str, ...] capabilities: tuple[str, ...] document_uri: str version: str @classmethod def from_record(cls, record: SkillRecord) -> "SkillSummaryRecord": return cls( skill_id=record.skill_id, name=record.name, description=record.description, tags=record.tags, capabilities=record.capabilities, document_uri=record.document_uri, version=record.version, ) class PromptRecord(StrictFrozenModel): """Registry record containing a fully resolved prompt document.""" prompt_id: str name: str description: str version: str tags: tuple[str, ...] capabilities: tuple[str, ...] arguments: Mapping[str, PromptArgumentEntry] = Field(default_factory=frozen_mapping) document_uri: str document_relpath: DocsPath document_content: str @field_validator("arguments", mode="before") @classmethod def freeze_arguments(cls, value: Mapping[str, PromptArgumentEntry] | None) -> Mapping[str, PromptArgumentEntry]: return frozen_mapping(value) class PromptSummaryRecord(StrictFrozenModel): """Compact prompt summary exposed by catalog listing APIs.""" prompt_id: str name: str description: str tags: tuple[str, ...] capabilities: tuple[str, ...] document_uri: str version: str @classmethod def from_record(cls, record: PromptRecord) -> "PromptSummaryRecord": return cls( prompt_id=record.prompt_id, name=record.name, description=record.description, tags=record.tags, capabilities=record.capabilities, document_uri=record.document_uri, version=record.version, ) class SkillPatternPayload(StrictFrozenModel): """Catalog payload model for skill pattern search results.""" id: str name: str version: str description: str tags: list[str] capabilities: list[str] resources: list[str] @classmethod def from_record(cls, record: SkillRecord) -> "SkillPatternPayload": return cls( id=record.skill_id, name=record.name, version=record.version, description=record.description, tags=list(record.tags), capabilities=list(record.capabilities), resources=list(record.capabilities), ) class SkillSummaryPayload(StrictFrozenModel): """Catalog payload model for skill index summaries.""" id: str name: str description: str tags: list[str] capabilities: list[str] version: str document_uri: str detail_uri: str resources: dict[str, str | list[str]] @classmethod def from_record(cls, record: SkillRecord) -> "SkillSummaryPayload": return cls( id=record.skill_id, name=record.name, description=record.description, tags=list(record.tags), capabilities=list(record.capabilities), version=record.version, document_uri=record.document_uri, detail_uri=f"resource://catalog/skills/{record.skill_id}", resources={ "document": record.document_uri, "references": [ f"resource://skills/{record.skill_id}/references/{ref_id}" for ref_id in sorted(record.references) ], }, ) class PromptSummaryPayload(StrictFrozenModel): """Catalog payload model for prompt index summaries.""" id: str name: str description: str tags: list[str] capabilities: list[str] version: str document_uri: str detail_uri: str @classmethod def from_record(cls, record: PromptRecord) -> "PromptSummaryPayload": return cls( id=record.prompt_id, name=record.name, description=record.description, tags=list(record.tags), capabilities=list(record.capabilities), version=record.version, document_uri=record.document_uri, detail_uri=f"resource://catalog/prompts/{record.prompt_id}", ) class DocsRegistry(StrictFrozenModel): """In-memory index of loaded skills, prompts, and docs content.""" skills_by_id: Mapping[str, SkillRecord] = Field(default_factory=frozen_mapping) skills_in_load_order: tuple[str, ...] skills_summary_in_load_order: tuple[SkillSummaryRecord, ...] docs_markdown_by_path: Mapping[DocsPath, str] = Field(default_factory=_empty_docs_mapping) docs_markdown_path_index: tuple[DocsPath, ...] tag_to_skill_ids: Mapping[str, tuple[str, ...]] = Field(default_factory=frozen_mapping) capability_to_skill_ids: Mapping[str, tuple[str, ...]] = Field(default_factory=frozen_mapping) prompts_by_id: Mapping[str, PromptRecord] = Field(default_factory=frozen_mapping) prompts_in_load_order: tuple[str, ...] = () prompts_summary_in_load_order: tuple[PromptSummaryRecord, ...] = () tag_to_prompt_ids: Mapping[str, tuple[str, ...]] = Field(default_factory=frozen_mapping) @field_validator( "skills_by_id", "docs_markdown_by_path", "tag_to_skill_ids", "capability_to_skill_ids", "prompts_by_id", "tag_to_prompt_ids", mode="before", ) @classmethod def freeze_mappings(cls, value: Mapping[str, object] | None) -> Mapping[str, object]: return frozen_mapping(value)