"""Manifest discovery helpers.""" from __future__ import annotations from dataclasses import dataclass from typing import Optional from urllib.parse import urljoin, urlparse from bs4 import BeautifulSoup from ..models import Manifest LINK_REL = "agent-permissions" DEFAULT_WELL_KNOWN_PATH = "/.well-known/agent-permissions.json" @dataclass class ManifestDiscoveryResult: manifest_url: Optional[str] method: str base_url: Optional[str] details: dict manifest: Optional[Manifest] = None def found(self) -> bool: return self.manifest_url is not None def _sanitize_base(url: str) -> str: parsed = urlparse(url) if not parsed.scheme or not parsed.netloc: raise ValueError(f"URL must include scheme and host: {url}") return f"{parsed.scheme}://{parsed.netloc}" def find_manifest_in_html(html: str, base_url: Optional[str] = None) -> ManifestDiscoveryResult: """Search HTML markup for a link tag referencing the manifest.""" soup = BeautifulSoup(html, "html.parser") link = soup.find("link", rel=lambda value: value and LINK_REL in value.split()) if link and link.has_attr("href"): href = link["href"] manifest_url = urljoin(base_url, href) if base_url else href return ManifestDiscoveryResult( manifest_url=manifest_url, method="html_link_tag", base_url=base_url, details={"href": href}, ) return ManifestDiscoveryResult( manifest_url=None, method="html_link_tag", base_url=base_url, details={"reason": "not_found"}, ) def fallback_well_known(url: str) -> ManifestDiscoveryResult: """Return the default /.well-known manifest location for a URL.""" base = _sanitize_base(url) manifest_url = urljoin(base, DEFAULT_WELL_KNOWN_PATH) return ManifestDiscoveryResult( manifest_url=manifest_url, method="default_well_known", base_url=base, details={}, )