"""Contracts for the PDF Create Agent. The agent accepts a natural-language prompt and returns a single CREATE_PDF_FROM_HTML_AGENT plan step carrying the rendered HTML. Pipeline: 1. PlannerAgent (smart_model) → DocumentPlan: structured skeleton, no body text. 2. Python chunks the plan by token budget. 3. SectionWriterAgents (smart_model, parallel) → WrittenSections per chunk. 4. Assembler collects sections in plan order → GeneratedDocument. 5. Jinja renders GeneratedDocument → HTML. The LLM never writes HTML. """ from __future__ import annotations import re from enum import StrEnum from typing import Annotated, Literal from pydantic import Field, field_validator from stirling.models import ApiModel from .common import ConversationMessage from .pdf_edit import EditCannotDoResponse, EditPlanResponse class SectionType(StrEnum): TEXT = "text" KEY_VALUE = "key_value" LINE_ITEMS = "line_items" BULLET_LIST = "bullet_list" SIGNATURE = "signature" class TextSection(ApiModel): """One or more prose paragraphs. Use for introductions, summaries, and narrative content.""" type: Literal[SectionType.TEXT] = SectionType.TEXT heading: str | None = None body: str = Field(description="Paragraph text. Use \\n\\n to separate paragraphs.") class KeyValueSection(ApiModel): """Labelled fields. Use for contact info, dates, invoice details, and metadata.""" type: Literal[SectionType.KEY_VALUE] = SectionType.KEY_VALUE heading: str | None = None pairs: list[tuple[str, str]] = Field(description="List of (label, value) pairs.") class LineItemsSection(ApiModel): """A table with column headers and data rows. Use for invoices, expenses, schedules.""" type: Literal[SectionType.LINE_ITEMS] = SectionType.LINE_ITEMS heading: str | None = None columns: list[str] = Field(description="Column header names.") rows: list[list[str]] = Field(description="Data rows; each row must match columns in length.") total_row: list[str] | None = None class BulletListSection(ApiModel): """An unordered list. Use for requirements, responsibilities, or any enumerated items.""" type: Literal[SectionType.BULLET_LIST] = SectionType.BULLET_LIST heading: str | None = None items: list[str] class SignatureSection(ApiModel): """Signature blocks. Use when the document requires sign-off from named parties.""" type: Literal[SectionType.SIGNATURE] = SectionType.SIGNATURE heading: str | None = None signatories: list[str] = Field(description="Names or roles to sign, e.g. 'John Smith, CEO'.") type DocumentSection = Annotated[ TextSection | KeyValueSection | LineItemsSection | BulletListSection | SignatureSection, Field(discriminator="type"), ] # Named colour or hex only — anything else is dropped so a colour can't inject CSS into the #