"""Prompt sections for %%there ai."""
from collections.abc import Iterable
from dataclasses import dataclass, field
from importlib.resources import files
DEFAULT_AI_TEMPLATE_RESOURCE = "prompts/default.md"
FIX_AI_TEMPLATE_RESOURCE = "prompts/fix.md"
DEFAULT_AI_PROMPT = "default"
FIX_AI_PROMPT = "fix"
class AIPromptError(RuntimeError):
"""Raised when %%there ai prompt composition fails."""
@dataclass
class AIPromptStore:
"""Mutable prompt definitions and active prompt stack.
Built-in prompts live in the same registry as user prompts so
register_ai_prompt() can intentionally override any prompt section.
"""
active_prompts: tuple[str, ...] | None = None
registry: dict[str, str] = field(default_factory=dict)
def _read_prompt_resource(name: str) -> str:
return (
files("herethere.there.ai").joinpath(name).read_text(encoding="utf-8").strip()
)
def _builtin_prompt_registry() -> dict[str, str]:
return {
DEFAULT_AI_PROMPT: _read_prompt_resource(DEFAULT_AI_TEMPLATE_RESOURCE),
FIX_AI_PROMPT: _read_prompt_resource(FIX_AI_TEMPLATE_RESOURCE),
}
_ai_prompt_store = AIPromptStore(registry=_builtin_prompt_registry())
def reset_ai_prompt_store() -> None:
"""Reset active prompt state and restore built-in prompt definitions."""
_ai_prompt_store.active_prompts = None
_ai_prompt_store.registry.clear()
_ai_prompt_store.registry.update(_builtin_prompt_registry())
def _normalize_prompt_name(name: str) -> str:
normalized = name.strip()
if not normalized:
raise ValueError("AI prompt name cannot be empty")
return normalized
def _normalize_prompt_text(text: str) -> str:
normalized = text.strip()
if not normalized:
raise ValueError("AI prompt cannot be empty")
return normalized
def _split_prompt_names(value: str) -> tuple[str, ...]:
names = []
for part in value.split(","):
normalized = part.strip()
if normalized:
names.append(normalized)
return tuple(names)
def _dedupe_prompt_names(names: Iterable[str]) -> tuple[str, ...]:
seen = set()
deduped = []
for name in names:
normalized = _normalize_prompt_name(name)
if normalized not in seen:
deduped.append(normalized)
seen.add(normalized)
return tuple(deduped)
[docs]
def register_ai_prompt(name: str, prompt: str) -> None:
"""Register or override a reusable prompt section."""
_ai_prompt_store.registry[_normalize_prompt_name(name)] = _normalize_prompt_text(
prompt
)
[docs]
def list_ai_prompts() -> tuple[str, ...]:
"""Return registered and built-in prompt section names."""
return tuple(sorted(_ai_prompt_store.registry))
[docs]
def get_ai_prompt(name: str) -> str:
"""Return one registered or built-in prompt section."""
normalized = _normalize_prompt_name(name)
try:
return _ai_prompt_store.registry[normalized]
except KeyError as exc:
raise AIPromptError(f"Unknown %%there ai prompt: {normalized!r}") from exc
[docs]
def set_ai_prompts(*names: str, include_default: bool = True) -> None:
"""Set the session prompt stack used by %%there ai."""
if len(names) == 1 and "," in names[0]:
names = _split_prompt_names(names[0])
prompt_names = build_ai_prompt_names(names, include_default=include_default)
_ai_prompt_store.active_prompts = prompt_names
[docs]
def clear_ai_prompts() -> None:
"""Clear session-level %%there ai prompt overrides."""
_ai_prompt_store.active_prompts = None
def build_ai_prompt_names(
prompt_names: Iterable[str] | None = None,
*,
include_default: bool = True,
) -> tuple[str, ...]:
"""Build an ordered, deduplicated prompt stack."""
names = list(prompt_names or ())
if include_default:
names.insert(0, DEFAULT_AI_PROMPT)
deduped = _dedupe_prompt_names(names)
if not deduped:
raise ValueError("AI prompt stack cannot be empty")
return deduped
[docs]
def build_ai_template(
prompt_names: Iterable[str] | None = None,
*,
include_default: bool = True,
) -> str:
"""Compose the named prompt sections into one system prompt."""
names = build_ai_prompt_names(prompt_names, include_default=include_default)
return "\n\n".join(get_ai_prompt(name) for name in names)
def resolve_ai_prompt_options(
prompt_names: Iterable[str] | None = None,
*,
include_default: bool = True,
config_prompt_names: Iterable[str] | None = None,
) -> tuple[tuple[str, ...] | None, bool]:
"""Resolve command, session, and config prompt options in precedence order."""
if prompt_names is not None:
return _dedupe_prompt_names(prompt_names), include_default
session_prompts = _ai_prompt_store.active_prompts
if session_prompts is not None:
return session_prompts, False
if config_prompt_names is not None:
return _dedupe_prompt_names(config_prompt_names), True
return None, True
def get_ai_template(
prompt_names: Iterable[str] | None = None,
*,
include_default: bool = True,
) -> str:
if prompt_names is not None:
return build_ai_template(prompt_names, include_default=include_default)
session_prompts = _ai_prompt_store.active_prompts
if session_prompts is not None:
return build_ai_template(session_prompts, include_default=False)
return build_ai_template()
def build_messages(
user_request: str,
prompt_names: Iterable[str] | None = None,
*,
include_default: bool = True,
) -> list[dict[str, str]]:
template = get_ai_template(
prompt_names,
include_default=include_default,
)
return [
{"role": "system", "content": template},
{"role": "user", "content": user_request.strip()},
]