diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2df9e1d..ed009eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,15 +35,6 @@ jobs: - name: Run mypy type checker run: uv run mypy src/ - - name: Check AdCP schemas are up to date - run: | - uv run python scripts/generate_schemas.py - if ! git diff --exit-code src/creative_agent/schemas_generated/; then - echo "❌ Generated schemas are out of sync!" - echo "Run: python scripts/generate_schemas.py" - exit 1 - fi - - name: Run smoke tests run: uv run pytest tests/smoke/ -v --no-cov -m smoke diff --git a/pyproject.toml b/pyproject.toml index 14dd3d5..4da32a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "boto3>=1.35.0", "markdown>=3.6", "bleach>=6.3.0", + "adcp>=1.2.1", # Official ADCP Python client for schema types ] [project.scripts] @@ -39,9 +40,6 @@ dev = [ "pytest-cov>=6.2.1", "pytest-mock>=3.14.1", "ruff>=0.8.0", - # Schema generation - "datamodel-code-generator>=0.26.0", - "jsonref>=1.1.0", # Type stubs "boto3-stubs[s3]>=1.35.0", "types-pillow>=10.0.0", @@ -59,7 +57,6 @@ exclude = [ ".venv", "build", "dist", - "src/creative_agent/schemas_generated", ] [tool.ruff.lint] @@ -137,7 +134,6 @@ omit = [ "tests/*", "*/__pycache__/*", "*/.venv/*", - "src/creative_agent/schemas_generated/*", ] [tool.coverage.report] diff --git a/scripts/generate_schemas.py b/scripts/generate_schemas.py deleted file mode 100755 index 11f8082..0000000 --- a/scripts/generate_schemas.py +++ /dev/null @@ -1,317 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate Pydantic models from AdCP JSON schemas. - -This script uses datamodel-code-generator to auto-generate Pydantic models -from the official AdCP JSON schemas cached in tests/schemas/v1/. - -The script handles $ref resolution by creating a custom loader that maps -the official $ref paths to our flattened file structure. - -Usage: - python scripts/generate_schemas.py [--output OUTPUT_FILE] - -The generated models should match the official AdCP spec exactly. -""" - -import argparse -import json -import subprocess -import sys -from pathlib import Path -from typing import Optional - -import httpx - - -def load_schema_with_resolver(schema_path: Path, schema_dir: Path) -> dict: - """ - Load a schema and create a custom loader for $ref resolution. - - This function creates a loader that maps AdCP $ref paths like - "/schemas/v1/enums/pacing.json" to our flattened file structure - "_schemas_v1_enums_pacing_json.json". - """ - - def ref_to_filename(ref: str) -> str: - """Convert $ref path to our flattened filename format.""" - # /schemas/v1/enums/pacing.json -> _schemas_v1_enums_pacing_json.json - return ref.replace("/", "_").replace(".", "_") + ".json" - - def load_ref(ref: str) -> dict: - """Load a schema from a $ref path.""" - filename = ref_to_filename(ref) - ref_path = schema_dir / filename - - if not ref_path.exists(): - raise FileNotFoundError(f"Referenced schema not found: {ref} (looked for {ref_path})") - - with open(ref_path) as f: - return json.load(f) - - return load_ref - - -def download_missing_schema(ref: str, schema_dir: Path) -> bool: - """ - Download a missing schema from AdCP website. - - Returns True if download successful, False otherwise. - """ - # Validate ref starts with /schemas/v1/ - if not ref.startswith("/schemas/v1/"): - print(f" ⚠️ Invalid schema ref (must start with /schemas/v1/): {ref}", file=sys.stderr) - return False - - # Prevent path traversal - if ".." in ref or ref.count("//") > 0: - print(f" ⚠️ Invalid schema ref (contains path traversal): {ref}", file=sys.stderr) - return False - - base_url = "https://adcontextprotocol.org" - schema_url = f"{base_url}{ref}" - ref_filename = ref.replace("/", "_").replace(".", "_") + ".json" - ref_path = schema_dir / ref_filename - - try: - print(f" 📥 Downloading missing schema: {ref}") - response = httpx.get(schema_url, timeout=10.0) - response.raise_for_status() - - schema = response.json() - - # Save to cache - with open(ref_path, "w") as f: - json.dump(schema, f, indent=2) - - print(f" ✅ Downloaded: {ref_filename}") - return True - - except Exception as e: - print(f" ❌ Failed to download {ref}: {e}", file=sys.stderr) - return False - - -def resolve_refs_in_schema(schema: dict, schema_dir: Path, visited: Optional[set] = None) -> dict: - """ - Recursively resolve all $ref references in a schema. - - Returns a new schema dict with all references inlined. - Downloads missing schemas from AdCP website automatically. - """ - if visited is None: - visited = set() - - # Handle $ref - if "$ref" in schema: - ref = schema["$ref"] - - # Avoid circular references - if ref in visited: - return {"description": f"Circular reference to {ref}"} - - visited.add(ref) - - # Load referenced schema - ref_filename = ref.replace("/", "_").replace(".", "_") + ".json" - ref_path = schema_dir / ref_filename - - if not ref_path.exists(): - # Try downloading missing schema - if not download_missing_schema(ref, schema_dir): - print(f"⚠️ Warning: Cannot resolve $ref: {ref}", file=sys.stderr) - return schema - - with open(ref_path) as f: - ref_schema = json.load(f) - - # Recursively resolve references in the loaded schema - resolved = resolve_refs_in_schema(ref_schema, schema_dir, visited) - - # Merge any properties from original schema (e.g., description) - for key, value in schema.items(): - if key != "$ref" and key not in resolved: - resolved[key] = value - - return resolved - - # Recursively process nested schemas - result = {} - for key, value in schema.items(): - if isinstance(value, dict): - result[key] = resolve_refs_in_schema(value, schema_dir, visited) - elif isinstance(value, list): - result[key] = [ - resolve_refs_in_schema(item, schema_dir, visited) if isinstance(item, dict) else item for item in value - ] - else: - result[key] = value - - return result - - -def generate_schemas_from_json(schema_dir: Path, output_file: Path): - """ - Generate Pydantic models from JSON schemas with proper $ref resolution. - """ - print(f"📂 Processing schemas from: {schema_dir}") - - # Create temporary directory for resolved schemas - temp_dir = Path("temp_resolved_schemas") - temp_dir.mkdir(exist_ok=True) - - try: - # Process each JSON schema file in sorted order for deterministic output - schema_files = sorted(schema_dir.glob("*.json")) - print(f"📝 Found {len(schema_files)} schema files") - - # Skip these non-schema files - skip_files = {"index.json", "SCHEMAS_INFO.md"} - - for schema_file in schema_files: - if schema_file.name in skip_files: - continue - - print(f" Processing: {schema_file.name}") - - # Load and resolve all $refs - with open(schema_file) as f: - schema = json.load(f) - - resolved_schema = resolve_refs_in_schema(schema, schema_dir) - - # Write resolved schema to temp directory - temp_file = temp_dir / schema_file.name - with open(temp_file, "w") as f: - json.dump(resolved_schema, f, indent=2) - - print(f"✅ Resolved all $refs, generated {len(list(temp_dir.glob('*.json')))} schemas") - - # Now run datamodel-codegen on resolved schemas - print("\n🔧 Generating Pydantic models...") - - cmd = [ - "datamodel-codegen", - "--input", - str(temp_dir), - "--output", - str(output_file), - "--input-file-type", - "jsonschema", - "--output-model-type", - "pydantic_v2.BaseModel", - "--use-annotated", - "--field-constraints", - "--use-standard-collections", - "--collapse-root-models", - "--use-double-quotes", - "--snake-case-field", - "--target-python-version", - "3.12", - "--disable-timestamp", - "--reuse-model", # Reuse models with same content for deterministic class names - ] - - result = subprocess.run(cmd, capture_output=True, text=True, check=False) - - if result.returncode != 0: - print("❌ Generation failed:", file=sys.stderr) - print(result.stderr, file=sys.stderr) - sys.exit(1) - - print(f"✅ Generated Pydantic models: {output_file}") - - # Add header comment to __init__.py - init_file = output_file / "__init__.py" - if not init_file.exists(): - init_file.touch() - - header = '''""" -Auto-generated Pydantic models from AdCP JSON schemas. - -⚠️ DO NOT EDIT FILES IN THIS DIRECTORY MANUALLY! - -Generated from: tests/schemas/v1/ -Generator: scripts/generate_schemas.py -Tool: datamodel-code-generator + custom $ref resolution - -To regenerate: - python scripts/generate_schemas.py - -Source: https://adcontextprotocol.org/schemas/v1/ -AdCP Version: v2.4 (schemas v1) -""" -''' - - with open(init_file, "w") as f: - f.write(header) - - print("✅ Added header to __init__.py") - - # Fix mypy issue with enum default in ProductCatalog - creative_asset_file = output_file / "_schemas_v1_core_creative_asset_json.py" - if creative_asset_file.exists(): - content = creative_asset_file.read_text() - # Add type: ignore comment to the problematic line - content = content.replace( - '] = "google_merchant_center"', - '] = "google_merchant_center" # type: ignore[assignment]', - ) - creative_asset_file.write_text(content) - print("✅ Fixed mypy issue in creative-asset schema") - - # Fix mypy issue with webhook method default in build-creative-response - build_response_file = output_file / "_schemas_v1_media_buy_build_creative_response_json.py" - if build_response_file.exists(): - content = build_response_file.read_text() - # Add type: ignore comment to the problematic line - content = content.replace( - 'Field(description="HTTP method")] = "POST"', - 'Field(description="HTTP method")] = "POST" # type: ignore[assignment]', - ) - build_response_file.write_text(content) - print("✅ Fixed mypy issue in build-creative-response schema") - - finally: - # Clean up temp directory - import shutil - - if temp_dir.exists(): - shutil.rmtree(temp_dir) - print("🧹 Cleaned up temporary files") - - -def main(): - parser = argparse.ArgumentParser(description="Generate Pydantic models from AdCP JSON schemas") - parser.add_argument( - "--output", - type=Path, - default=Path("src/creative_agent/schemas_generated"), - help="Output directory for generated schemas (default: src/creative_agent/schemas_generated/)", - ) - parser.add_argument( - "--schema-dir", - type=Path, - default=Path("tests/schemas/v1"), - help="Directory containing JSON schemas (default: tests/schemas/v1)", - ) - args = parser.parse_args() - - if not args.schema_dir.exists(): - print(f"❌ Schema directory not found: {args.schema_dir}", file=sys.stderr) - sys.exit(1) - - # Create output directory if needed - args.output.parent.mkdir(parents=True, exist_ok=True) - - generate_schemas_from_json(args.schema_dir, args.output) - - print("\n📊 Next steps:") - print(" 1. Review generated schemas in", args.output) - print(" 2. Compare with manual schemas in src/creative_agent/schemas/") - print(" 3. Identify which models to use (generated vs manual)") - print(" 4. Run tests to ensure compatibility") - - -if __name__ == "__main__": - main() diff --git a/scripts/update_schemas.py b/scripts/update_schemas.py deleted file mode 100644 index f8dc98e..0000000 --- a/scripts/update_schemas.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python3 -""" -Update local schema cache from AdCP website. - -This script downloads all AdCP JSON schemas from adcontextprotocol.org -and updates the local cache in tests/schemas/v1/. - -Usage: - python scripts/update_schemas.py [--dry-run] -""" - -import argparse -import json -import sys -from pathlib import Path -from typing import Optional, Union - -import httpx - - -def filename_to_ref(filename: str) -> str: - """Convert our flattened filename format to a $ref path.""" - # _schemas_v1_core_format_json.json -> /schemas/v1/core/format.json - name = filename.replace(".json", "").replace("_json", ".json").replace("_", "/", 1) - return name - - -def ref_to_filename(ref: str) -> str: - """Convert $ref path to our flattened filename format.""" - # /schemas/v1/core/format.json -> _schemas_v1_core_format_json.json - return ref.replace("/", "_").replace(".", "_") + ".json" - - -def download_schema(ref: str, base_url: str = "https://adcontextprotocol.org") -> Optional[dict]: - """ - Download a schema from AdCP website. - - Returns schema dict if successful, None if not found or error. - """ - schema_url = f"{base_url}{ref}" - - try: - print(f" Fetching: {ref}") - response = httpx.get(schema_url, timeout=10.0, follow_redirects=True) - response.raise_for_status() - - # Check if we got JSON (not HTML) - content_type = response.headers.get("content-type", "") - if "json" not in content_type.lower(): - print(f" ⚠️ Skipping {ref}: Got {content_type} instead of JSON") - return None - - schema = response.json() - return schema - - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - print(f" ⚠️ Not found: {ref}") - else: - print(f" ❌ HTTP {e.response.status_code}: {ref}") - return None - except Exception as e: - print(f" ❌ Error downloading {ref}: {e}") - return None - - -def is_creative_agent_schema(ref: str) -> bool: - """ - Check if a schema is relevant for a Creative Agent. - - Creative agents only need schemas related to creative formats, assets, - and creative agent tools - not media buy, signals, or other protocol areas. - """ - creative_patterns = [ - "/schemas/v1/core/assets/", # All asset types - "/schemas/v1/core/creative-", # Creative-specific schemas - "/schemas/v1/core/format", # Format and format-id - "/schemas/v1/core/brand-manifest", # Brand manifest schemas - "/schemas/v1/creative/", # Creative agent tool schemas - "/schemas/v1/enums/", # Shared enums (needed by assets and formats) - "/schemas/v1/standard-formats/", # Standard format definitions - "/schemas/v1/adagents.json", # Agent capabilities - "/schemas/v1/core/response.json", # Protocol response wrapper - "/schemas/v1/core/error.json", # Error schema - "/schemas/v1/core/sub-asset.json", # Sub-asset for carousels - ] - - return any(pattern in ref for pattern in creative_patterns) - - -def discover_schemas(schema_dir: Path, creative_only: bool = True) -> list: - """ - Discover all schema $refs from existing cache. - - Args: - schema_dir: Directory containing cached schemas - creative_only: If True, only return creative-agent-relevant schemas - - Returns list of unique $ref paths found in existing schemas. - """ - refs = set() - - for schema_file in schema_dir.glob("*.json"): - try: - with open(schema_file) as f: - schema = json.load(f) - - # Extract $ref from this schema - if "$id" in schema: - schema_ref = schema["$id"] - if not creative_only or is_creative_agent_schema(schema_ref): - refs.add(schema_ref) - - # Recursively find all $refs in the schema - all_refs = find_refs_in_schema(schema) - if creative_only: - all_refs = {r for r in all_refs if is_creative_agent_schema(r)} - refs.update(all_refs) - - except Exception as e: - print(f" ⚠️ Error reading {schema_file.name}: {e}") - - return sorted(refs) - - -def find_refs_in_schema(obj: Union[dict, list]) -> set: - """Recursively find all $ref values in a schema.""" - refs = set() - - if isinstance(obj, dict): - if "$ref" in obj: - refs.add(obj["$ref"]) - for value in obj.values(): - refs.update(find_refs_in_schema(value)) - elif isinstance(obj, list): - for item in obj: - refs.update(find_refs_in_schema(item)) - - return refs - - -def update_schemas(schema_dir: Path, dry_run: bool = False, creative_only: bool = True): - """ - Update schemas from AdCP website. - - Discovers schema refs from existing cache, downloads latest versions, - and updates local files. - - Args: - schema_dir: Directory containing cached schemas - dry_run: If True, show what would change without modifying files - creative_only: If True, only update creative-agent-relevant schemas - """ - print(f"📂 Schema directory: {schema_dir}") - if creative_only: - print("🎨 Filtering to creative-agent-relevant schemas only") - - if not schema_dir.exists(): - print(f"❌ Directory not found: {schema_dir}") - sys.exit(1) - - # Discover all schema refs - print("\n🔍 Discovering schemas from existing cache...") - refs = discover_schemas(schema_dir, creative_only=creative_only) - print(f" Found {len(refs)} unique schema refs") - - # Download and update each schema - print("\n📥 Downloading latest schemas...") - updated = 0 - unchanged = 0 - failed = 0 - - for ref in refs: - # Validate ref - if not ref.startswith("/schemas/v1/"): - print(f" ⚠️ Skipping invalid ref: {ref}") - continue - - # Download latest version - latest_schema = download_schema(ref) - if latest_schema is None: - failed += 1 - continue - - # Compare with local version - filename = ref_to_filename(ref) - local_path = schema_dir / filename - - if local_path.exists(): - with open(local_path) as f: - local_schema = json.load(f) - - if local_schema == latest_schema: - print(f" ✓ No changes: {filename}") - unchanged += 1 - continue - - # Update local file - if dry_run: - print(f" 🔄 Would update: {filename}") - updated += 1 - else: - with open(local_path, "w") as f: - json.dump(latest_schema, f, indent=2) - f.write("\n") # Add trailing newline - print(f" ✅ Updated: {filename}") - updated += 1 - - # Summary - print(f"\n📊 Summary:") - print(f" Updated: {updated}") - print(f" Unchanged: {unchanged}") - print(f" Failed: {failed}") - - if dry_run: - print("\n (Dry run - no files were modified)") - - if updated > 0 and not dry_run: - print("\n💡 Next steps:") - print(" 1. Review changes: git diff tests/schemas/v1/") - print(" 2. Regenerate Python models: python scripts/generate_schemas.py") - print(" 3. Run tests: pytest") - - -def main(): - parser = argparse.ArgumentParser( - description="Update AdCP schemas from website (creative-agent-relevant schemas only by default)" - ) - parser.add_argument("--dry-run", action="store_true", help="Show what would be updated without making changes") - parser.add_argument( - "--schema-dir", - type=Path, - default=Path("tests/schemas/v1"), - help="Directory containing JSON schemas (default: tests/schemas/v1)", - ) - parser.add_argument( - "--all-schemas", - action="store_true", - help="Include all AdCP schemas (media buy, signals, etc.), not just creative-agent schemas", - ) - args = parser.parse_args() - - update_schemas(args.schema_dir, dry_run=args.dry_run, creative_only=not args.all_schemas) - - -if __name__ == "__main__": - main() diff --git a/src/creative_agent/api_server.py b/src/creative_agent/api_server.py index 59dae1f..6a45f6f 100644 --- a/src/creative_agent/api_server.py +++ b/src/creative_agent/api_server.py @@ -60,8 +60,9 @@ async def list_formats() -> list[dict[str, Any]]: async def get_format(format_id: str) -> dict[str, Any]: """Get a specific format by ID (assumes this agent's formats).""" + from adcp.types.generated import FormatId + from .data.standard_formats import AGENT_URL - from .schemas_generated._schemas_v1_core_format_json import FormatId # Convert string ID to FormatId object (assume our agent) fmt_id = FormatId(agent_url=AGENT_URL, id=format_id) @@ -76,8 +77,9 @@ async def get_format(format_id: str) -> dict[str, Any]: async def preview_creative(request: PreviewRequest) -> dict[str, Any]: """Generate preview from creative manifest.""" + from adcp.types.generated import FormatId + from .data.standard_formats import AGENT_URL - from .schemas_generated._schemas_v1_core_format_json import FormatId # Convert string ID to FormatId object (assume our agent) fmt_id = FormatId(agent_url=AGENT_URL, id=request.format_id) diff --git a/src/creative_agent/data/format_types.py b/src/creative_agent/data/format_types.py new file mode 100644 index 0000000..95cc023 --- /dev/null +++ b/src/creative_agent/data/format_types.py @@ -0,0 +1,80 @@ +"""Type definitions for building Format objects. + +These types mirror the structure expected by Format.assets_required and Format.renders, +but are defined locally since the adcp library uses flexible Any types for these fields. +""" + +from enum import Enum + +from pydantic import BaseModel, Field + + +class Type(Enum): + """Media type of creative format.""" + + audio = "audio" + video = "video" + display = "display" + native = "native" + dooh = "dooh" + rich_media = "rich_media" + universal = "universal" + + +class AssetType(Enum): + """Type of asset required by a format.""" + + image = "image" + video = "video" + audio = "audio" + vast = "vast" + daast = "daast" + text = "text" + markdown = "markdown" + html = "html" + css = "css" + javascript = "javascript" + url = "url" + webhook = "webhook" + promoted_offerings = "promoted_offerings" + + +class Unit(Enum): + """Measurement unit for dimensions.""" + + px = "px" + dp = "dp" + inches = "inches" + cm = "cm" + + +class Responsive(BaseModel): + """Responsive sizing flags.""" + + width: bool + height: bool + + +class Dimensions(BaseModel): + """Dimensions specification for a render.""" + + width: float | None = Field(None, description="Fixed width in specified units", ge=0.0) + height: float | None = Field(None, description="Fixed height in specified units", ge=0.0) + responsive: Responsive + unit: Unit + + +class Render(BaseModel): + """Specification for a single rendered piece.""" + + role: str = Field(description="Semantic role (e.g., 'primary', 'companion')") + dimensions: Dimensions + + +class AssetsRequired(BaseModel): + """Specification for a required asset.""" + + asset_id: str = Field(description="Identifier for this asset") + asset_type: AssetType + required: bool = True + requirements: dict[str, str | int | float | bool | list[str]] | None = None diff --git a/src/creative_agent/data/standard_formats.py b/src/creative_agent/data/standard_formats.py index 39db7b1..ec3a279 100644 --- a/src/creative_agent/data/standard_formats.py +++ b/src/creative_agent/data/standard_formats.py @@ -3,14 +3,15 @@ # mypy: disable-error-code="call-arg" # Pydantic models with extra='forbid' trigger false positives when optional fields aren't passed -from pydantic import AnyUrl +from typing import Any + +from adcp.types.generated import FormatId from ..schemas import CreativeFormat -from ..schemas_generated._schemas_v1_core_format_json import ( +from .format_types import ( AssetsRequired, AssetType, Dimensions, - FormatId, Render, Responsive, Type, @@ -18,7 +19,7 @@ ) # Agent configuration -AGENT_URL = AnyUrl("https://creative.adcontextprotocol.org") +AGENT_URL = "https://creative.adcontextprotocol.org" AGENT_NAME = "AdCP Standard Creative Agent" AGENT_CAPABILITIES = ["validation", "assembly", "generation", "preview"] @@ -42,9 +43,27 @@ def create_format_id(format_name: str) -> FormatId: return FormatId(agent_url=AGENT_URL, id=format_name) -def create_fixed_render(width: int, height: int, role: str = "primary") -> Render: +def create_asset_required( + asset_id: str, + asset_type: AssetType, + required: bool = True, + requirements: dict[str, str | int | float | bool | list[str]] | None = None, +) -> dict[str, str | bool | dict[str, str | int | float | bool | list[str]] | AssetType]: + """Create an assets_required entry as a dict for the Format model.""" + asset = AssetsRequired( + asset_id=asset_id, + asset_type=asset_type, + required=required, + requirements=requirements, + ) + return asset.model_dump(mode="json") + + +def create_fixed_render( + width: int, height: int, role: str = "primary" +) -> dict[str, str | dict[str, float | bool | Responsive | Unit]]: """Create a render with fixed dimensions (non-responsive).""" - return Render( + render = Render( role=role, dimensions=Dimensions( width=width, @@ -53,11 +72,14 @@ def create_fixed_render(width: int, height: int, role: str = "primary") -> Rende unit=Unit.px, ), ) + return render.model_dump(mode="json") -def create_responsive_render(role: str = "primary") -> Render: +def create_responsive_render( + role: str = "primary", +) -> dict[str, str | dict[str, float | None | bool | Responsive | Unit]]: """Create a render with responsive dimensions.""" - return Render( + render = Render( role=role, dimensions=Dimensions( width=None, @@ -66,6 +88,7 @@ def create_responsive_render(role: str = "primary") -> Render: unit=Unit.px, ), ) + return render.model_dump(mode="json") # Generative Formats - AI-powered creative generation @@ -74,19 +97,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x250_generative"), name="Medium Rectangle - AI Generated", - type=Type.display, + type="display", description="AI-generated 300x250 banner from brand context and prompt", renders=[create_fixed_render(300, 250)], output_format_ids=[create_format_id("display_300x250_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -97,19 +120,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_728x90_generative"), name="Leaderboard - AI Generated", - type=Type.display, + type="display", description="AI-generated 728x90 banner from brand context and prompt", renders=[create_fixed_render(728, 90)], output_format_ids=[create_format_id("display_728x90_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -120,19 +143,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_320x50_generative"), name="Mobile Banner - AI Generated", - type=Type.display, + type="display", description="AI-generated 320x50 mobile banner from brand context and prompt", renders=[create_fixed_render(320, 50)], output_format_ids=[create_format_id("display_320x50_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -143,19 +166,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_160x600_generative"), name="Wide Skyscraper - AI Generated", - type=Type.display, + type="display", description="AI-generated 160x600 wide skyscraper from brand context and prompt", renders=[create_fixed_render(160, 600)], output_format_ids=[create_format_id("display_160x600_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -166,19 +189,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_336x280_generative"), name="Large Rectangle - AI Generated", - type=Type.display, + type="display", description="AI-generated 336x280 large rectangle from brand context and prompt", renders=[create_fixed_render(336, 280)], output_format_ids=[create_format_id("display_336x280_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -189,19 +212,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x600_generative"), name="Half Page - AI Generated", - type=Type.display, + type="display", description="AI-generated 300x600 half page from brand context and prompt", renders=[create_fixed_render(300, 600)], output_format_ids=[create_format_id("display_300x600_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -212,19 +235,19 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_970x250_generative"), name="Billboard - AI Generated", - type=Type.display, + type="display", description="AI-generated 970x250 billboard from brand context and prompt", renders=[create_fixed_render(970, 250)], output_format_ids=[create_format_id("display_970x250_image")], supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="promoted_offerings", asset_type=AssetType.promoted_offerings, required=True, requirements={"description": "Brand manifest and product offerings for AI generation"}, ), - AssetsRequired( + create_asset_required( asset_id="generation_prompt", asset_type=AssetType.text, required=True, @@ -239,11 +262,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_standard_30s"), name="Standard Video - 30 seconds", - type=Type.video, + type="video", description="30-second video ad in standard aspect ratios", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -258,11 +281,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_standard_15s"), name="Standard Video - 15 seconds", - type=Type.video, + type="video", description="15-second video ad in standard aspect ratios", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -277,11 +300,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_vast_30s"), name="VAST Video - 30 seconds", - type=Type.video, + type="video", description="30-second video ad via VAST tag", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="vast_tag", asset_type=AssetType.text, required=True, @@ -294,12 +317,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_1920x1080"), name="Full HD Video - 1920x1080", - type=Type.video, + type="video", description="1920x1080 Full HD video (16:9)", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], renders=[create_fixed_render(1920, 1080)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -315,12 +338,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_1280x720"), name="HD Video - 1280x720", - type=Type.video, + type="video", description="1280x720 HD video (16:9)", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], renders=[create_fixed_render(1280, 720)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -336,12 +359,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_1080x1920"), name="Vertical Video - 1080x1920", - type=Type.video, + type="video", description="1080x1920 vertical video (9:16) for mobile stories", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], renders=[create_fixed_render(1080, 1920)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -357,12 +380,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_1080x1080"), name="Square Video - 1080x1080", - type=Type.video, + type="video", description="1080x1080 square video (1:1) for social feeds", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"], renders=[create_fixed_render(1080, 1080)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -378,11 +401,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_ctv_preroll_30s"), name="CTV Pre-Roll - 30 seconds", - type=Type.video, + type="video", description="30-second pre-roll ad for Connected TV and streaming platforms", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE", "PLAYER_SIZE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -397,11 +420,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("video_ctv_midroll_30s"), name="CTV Mid-Roll - 30 seconds", - type=Type.video, + type="video", description="30-second mid-roll ad for Connected TV and streaming platforms", supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE", "PLAYER_SIZE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="video_file", asset_type=AssetType.video, required=True, @@ -420,12 +443,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x250_image"), name="Medium Rectangle - Image", - type=Type.display, + type="display", description="300x250 static image banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 250)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -436,7 +459,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -449,12 +472,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_728x90_image"), name="Leaderboard - Image", - type=Type.display, + type="display", description="728x90 static image banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(728, 90)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -465,7 +488,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -475,12 +498,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_320x50_image"), name="Mobile Banner - Image", - type=Type.display, + type="display", description="320x50 mobile banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(320, 50)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -491,7 +514,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -501,12 +524,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_160x600_image"), name="Wide Skyscraper - Image", - type=Type.display, + type="display", description="160x600 wide skyscraper banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(160, 600)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -517,7 +540,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -527,12 +550,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_336x280_image"), name="Large Rectangle - Image", - type=Type.display, + type="display", description="336x280 large rectangle banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(336, 280)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -543,7 +566,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -553,12 +576,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x600_image"), name="Half Page - Image", - type=Type.display, + type="display", description="300x600 half page banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 600)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -569,7 +592,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -579,12 +602,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_970x250_image"), name="Billboard - Image", - type=Type.display, + type="display", description="970x250 billboard banner", supported_macros=COMMON_MACROS, renders=[create_fixed_render(970, 250)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="banner_image", asset_type=AssetType.image, required=True, @@ -595,7 +618,7 @@ def create_responsive_render(role: str = "primary") -> Render: "acceptable_formats": ["jpg", "png", "gif", "webp"], }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -609,12 +632,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x250_html"), name="Medium Rectangle - HTML5", - type=Type.display, + type="display", description="300x250 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 250)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -630,12 +653,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_728x90_html"), name="Leaderboard - HTML5", - type=Type.display, + type="display", description="728x90 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(728, 90)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -650,12 +673,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_160x600_html"), name="Wide Skyscraper - HTML5", - type=Type.display, + type="display", description="160x600 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(160, 600)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -670,12 +693,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_336x280_html"), name="Large Rectangle - HTML5", - type=Type.display, + type="display", description="336x280 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(336, 280)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -690,12 +713,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_300x600_html"), name="Half Page - HTML5", - type=Type.display, + type="display", description="300x600 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 600)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -710,12 +733,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("display_970x250_html"), name="Billboard - HTML5", - type=Type.display, + type="display", description="970x250 HTML5 creative", supported_macros=COMMON_MACROS, renders=[create_fixed_render(970, 250)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="html_creative", asset_type=AssetType.html, required=True, @@ -734,11 +757,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("native_standard"), name="IAB Native Standard", - type=Type.native, + type="native", description="Standard native ad with title, description, image, and CTA", supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="title", asset_type=AssetType.text, required=True, @@ -746,7 +769,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Headline text (25 chars recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="description", asset_type=AssetType.text, required=True, @@ -754,7 +777,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Body copy (90 chars recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="main_image", asset_type=AssetType.image, required=True, @@ -762,7 +785,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Primary image (1200x627 recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="icon", asset_type=AssetType.image, required=False, @@ -770,7 +793,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Brand icon (square, 200x200 recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="cta_text", asset_type=AssetType.text, required=True, @@ -778,7 +801,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Call-to-action text", }, ), - AssetsRequired( + create_asset_required( asset_id="sponsored_by", asset_type=AssetType.text, required=True, @@ -791,11 +814,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("native_content"), name="Native Content Placement", - type=Type.native, + type="native", description="In-article native ad with editorial styling", supported_macros=COMMON_MACROS, assets_required=[ - AssetsRequired( + create_asset_required( asset_id="headline", asset_type=AssetType.text, required=True, @@ -803,7 +826,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Editorial-style headline (60 chars recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="body", asset_type=AssetType.text, required=True, @@ -811,7 +834,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Article-style body copy (200 chars recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="thumbnail", asset_type=AssetType.image, required=True, @@ -819,7 +842,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Thumbnail image (square, 300x300 recommended)", }, ), - AssetsRequired( + create_asset_required( asset_id="author", asset_type=AssetType.text, required=False, @@ -827,7 +850,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Author name for editorial context", }, ), - AssetsRequired( + create_asset_required( asset_id="click_url", asset_type=AssetType.url, required=True, @@ -835,7 +858,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Landing page URL", }, ), - AssetsRequired( + create_asset_required( asset_id="disclosure", asset_type=AssetType.text, required=True, @@ -852,11 +875,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("audio_standard_15s"), name="Standard Audio - 15 seconds", - type=Type.audio, + type="audio", description="15-second audio ad", supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="audio_file", asset_type=AssetType.audio, required=True, @@ -870,11 +893,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("audio_standard_30s"), name="Standard Audio - 30 seconds", - type=Type.audio, + type="audio", description="30-second audio ad", supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="audio_file", asset_type=AssetType.audio, required=True, @@ -888,11 +911,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("audio_standard_60s"), name="Standard Audio - 60 seconds", - type=Type.audio, + type="audio", description="60-second audio ad", supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="audio_file", asset_type=AssetType.audio, required=True, @@ -910,12 +933,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("dooh_billboard_1920x1080"), name="Digital Billboard - 1920x1080", - type=Type.dooh, + type="dooh", description="Full HD digital billboard", supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"], renders=[create_fixed_render(1920, 1080)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="billboard_image", asset_type=AssetType.image, required=True, @@ -930,11 +953,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("dooh_billboard_landscape"), name="Digital Billboard - Landscape", - type=Type.dooh, + type="dooh", description="Landscape-oriented digital billboard (various sizes)", supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="billboard_image", asset_type=AssetType.image, required=True, @@ -948,11 +971,11 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("dooh_billboard_portrait"), name="Digital Billboard - Portrait", - type=Type.dooh, + type="dooh", description="Portrait-oriented digital billboard (various sizes)", supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="billboard_image", asset_type=AssetType.image, required=True, @@ -966,12 +989,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("dooh_transit_screen"), name="Transit Screen", - type=Type.dooh, + type="dooh", description="Transit and subway screen displays", supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG", "TRANSIT_LINE"], renders=[create_fixed_render(1920, 1080)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="screen_image", asset_type=AssetType.image, required=True, @@ -991,12 +1014,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("product_card_standard"), name="Product Card - Standard", - type=Type.display, + type="display", description="Standard visual card (300x400px) for displaying ad inventory products", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 400)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="product_image", asset_type=AssetType.image, required=True, @@ -1004,7 +1027,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Primary product image or placement preview", }, ), - AssetsRequired( + create_asset_required( asset_id="product_name", asset_type=AssetType.text, required=True, @@ -1012,7 +1035,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Display name of the product (e.g., 'Homepage Leaderboard')", }, ), - AssetsRequired( + create_asset_required( asset_id="product_description", asset_type=AssetType.text, required=True, @@ -1020,7 +1043,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Short description of the product (supports markdown)", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_model", asset_type=AssetType.text, required=False, @@ -1028,7 +1051,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Pricing model (e.g., 'CPM', 'flat_rate', 'CPC')", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_amount", asset_type=AssetType.text, required=False, @@ -1036,7 +1059,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Price amount (e.g., '15.00')", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_currency", asset_type=AssetType.text, required=False, @@ -1044,7 +1067,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Currency code (e.g., 'USD')", }, ), - AssetsRequired( + create_asset_required( asset_id="delivery_type", asset_type=AssetType.text, required=False, @@ -1052,7 +1075,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Delivery type: 'guaranteed' or 'bidded'", }, ), - AssetsRequired( + create_asset_required( asset_id="primary_asset_type", asset_type=AssetType.text, required=False, @@ -1065,12 +1088,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("product_card_detailed"), name="Product Card - Detailed", - type=Type.display, + type="display", description="Detailed card with carousel and full specifications for rich product presentation", supported_macros=COMMON_MACROS, renders=[create_responsive_render()], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="product_image", asset_type=AssetType.image, required=True, @@ -1078,7 +1101,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Primary product image or placement preview", }, ), - AssetsRequired( + create_asset_required( asset_id="product_name", asset_type=AssetType.text, required=True, @@ -1086,7 +1109,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Display name of the product (e.g., 'Homepage Leaderboard')", }, ), - AssetsRequired( + create_asset_required( asset_id="product_description", asset_type=AssetType.text, required=True, @@ -1094,7 +1117,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Detailed description of the product (supports markdown)", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_model", asset_type=AssetType.text, required=False, @@ -1102,7 +1125,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Pricing model (e.g., 'CPM', 'flat_rate', 'CPC')", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_amount", asset_type=AssetType.text, required=False, @@ -1110,7 +1133,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Price amount (e.g., '15.00')", }, ), - AssetsRequired( + create_asset_required( asset_id="pricing_currency", asset_type=AssetType.text, required=False, @@ -1118,7 +1141,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Currency code (e.g., 'USD')", }, ), - AssetsRequired( + create_asset_required( asset_id="delivery_type", asset_type=AssetType.text, required=False, @@ -1126,7 +1149,7 @@ def create_responsive_render(role: str = "primary") -> Render: "description": "Delivery type: 'guaranteed' or 'bidded'", }, ), - AssetsRequired( + create_asset_required( asset_id="primary_asset_type", asset_type=AssetType.text, required=False, @@ -1139,12 +1162,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("format_card_standard"), name="Format Card - Standard", - type=Type.display, + type="display", description="Standard visual card (300x400px) for displaying creative formats in user interfaces", supported_macros=COMMON_MACROS, renders=[create_fixed_render(300, 400)], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="format", asset_type=AssetType.text, required=True, @@ -1157,12 +1180,12 @@ def create_responsive_render(role: str = "primary") -> Render: CreativeFormat( format_id=create_format_id("format_card_detailed"), name="Format Card - Detailed", - type=Type.display, + type="display", description="Detailed card with carousel and full specifications for rich format documentation", supported_macros=COMMON_MACROS, renders=[create_responsive_render()], assets_required=[ - AssetsRequired( + create_asset_required( asset_id="format", asset_type=AssetType.text, required=True, @@ -1219,7 +1242,7 @@ def filter_formats( if type: # Handle both Type enum and string values if isinstance(type, str): - results = [fmt for fmt in results if fmt.type.value == type] + results = [fmt for fmt in results if fmt.type == type] else: results = [fmt for fmt in results if fmt.type == type] @@ -1234,8 +1257,8 @@ def filter_formats( for fmt in results if fmt.renders and len(fmt.renders) > 0 - and fmt.renders[0].dimensions.width == target_width - and fmt.renders[0].dimensions.height == target_height + and fmt.renders[0].get("dimensions", {}).get("width") == target_width + and fmt.renders[0].get("dimensions", {}).get("height") == target_height ] except ValueError: pass # Invalid dimension format, skip filter @@ -1247,7 +1270,8 @@ def get_dimensions(fmt: CreativeFormat) -> tuple[float | None, float | None]: """Extract width and height from format renders.""" if fmt.renders and len(fmt.renders) > 0: render = fmt.renders[0] - return render.dimensions.width, render.dimensions.height + dimensions = render.get("dimensions", {}) + return dimensions.get("width"), dimensions.get("height") return None, None filtered = [] @@ -1277,8 +1301,11 @@ def get_dimensions(fmt: CreativeFormat) -> tuple[float | None, float | None]: for fmt in results if fmt.renders and len(fmt.renders) > 0 - and fmt.renders[0].dimensions.responsive - and (fmt.renders[0].dimensions.responsive.width or fmt.renders[0].dimensions.responsive.height) + and fmt.renders[0].get("dimensions", {}).get("responsive", {}) + and ( + fmt.renders[0].get("dimensions", {}).get("responsive", {}).get("width") + or fmt.renders[0].get("dimensions", {}).get("responsive", {}).get("height") + ) ] else: # Filter for non-responsive (fixed dimension) formats @@ -1287,9 +1314,9 @@ def get_dimensions(fmt: CreativeFormat) -> tuple[float | None, float | None]: for fmt in results if fmt.renders and len(fmt.renders) > 0 - and fmt.renders[0].dimensions.responsive - and not fmt.renders[0].dimensions.responsive.width - and not fmt.renders[0].dimensions.responsive.height + and fmt.renders[0].get("dimensions", {}).get("responsive", {}) + and not fmt.renders[0].get("dimensions", {}).get("responsive", {}).get("width") + and not fmt.renders[0].get("dimensions", {}).get("responsive", {}).get("height") ] if name_search: @@ -1298,20 +1325,21 @@ def get_dimensions(fmt: CreativeFormat) -> tuple[float | None, float | None]: if asset_types: # Filter to formats that include ALL specified asset types - def has_asset_type(req: AssetsRequired | object, target_type: AssetType | str) -> bool: + def has_asset_type(req: dict[str, Any], target_type: AssetType | str) -> bool: """Check if a requirement has the target asset type.""" - if isinstance(req, AssetsRequired): - if isinstance(target_type, AssetType): - return req.asset_type == target_type - return req.asset_type.value == target_type - # AssetsRequired1 - check assets within the group - if hasattr(req, "assets"): - for asset in req.assets: - if isinstance(target_type, AssetType): - if asset.asset_type == target_type: + # Handle dict format (from model_dump) + if isinstance(req, dict): + req_asset_type = req.get("asset_type") + # Compare string values + target_str = target_type.value if isinstance(target_type, AssetType) else target_type + if req_asset_type == target_str: + return True + # Check if it's a grouped asset requirement with assets array + if "assets" in req: + for asset in req["assets"]: + asset_type = asset.get("asset_type") + if asset_type == target_str: return True - elif asset.asset_type.value == target_type: - return True return False results = [ diff --git a/src/creative_agent/renderers/base.py b/src/creative_agent/renderers/base.py index 4a7eaa8..01801fb 100644 --- a/src/creative_agent/renderers/base.py +++ b/src/creative_agent/renderers/base.py @@ -35,11 +35,23 @@ def get_dimensions(self, format_obj: Any) -> tuple[int, int]: height = 250 if format_obj.renders and len(format_obj.renders) > 0: first_render = format_obj.renders[0] - if first_render.dimensions: - if first_render.dimensions.width is not None: - width = int(first_render.dimensions.width) - if first_render.dimensions.height is not None: - height = int(first_render.dimensions.height) + # Handle both dict and object access + dimensions = ( + first_render.get("dimensions") + if isinstance(first_render, dict) + else getattr(first_render, "dimensions", None) + ) + if dimensions: + dim_width = ( + dimensions.get("width") if isinstance(dimensions, dict) else getattr(dimensions, "width", None) + ) + dim_height = ( + dimensions.get("height") if isinstance(dimensions, dict) else getattr(dimensions, "height", None) + ) + if dim_width is not None: + width = int(dim_width) + if dim_height is not None: + height = int(dim_height) return width, height def get_manifest_assets(self, manifest: Any) -> dict[str, Any]: @@ -75,8 +87,14 @@ def build_asset_type_map(self, format_obj: Any) -> dict[str, str]: asset_type_map = {} if hasattr(format_obj, "assets_required") and format_obj.assets_required: for required_asset in format_obj.assets_required: - asset_id = getattr(required_asset, "asset_id", None) - asset_type = getattr(required_asset, "asset_type", None) + # Handle both dict and object access + if isinstance(required_asset, dict): + asset_id = required_asset.get("asset_id") + asset_type = required_asset.get("asset_type") + else: + asset_id = getattr(required_asset, "asset_id", None) + asset_type = getattr(required_asset, "asset_type", None) + if asset_id and asset_type: # Handle enum or string asset_type if hasattr(asset_type, "value"): diff --git a/src/creative_agent/schemas/__init__.py b/src/creative_agent/schemas/__init__.py index 6ce8dbc..c5ce0fa 100644 --- a/src/creative_agent/schemas/__init__.py +++ b/src/creative_agent/schemas/__init__.py @@ -1,34 +1,22 @@ """ AdCP schemas for creative agent. -This module re-exports official AdCP schemas from the auto-generated schemas_generated/ -directory, providing a clean interface for the rest of the codebase. +This module re-exports official AdCP schemas from the adcp library, +providing a clean interface for the rest of the codebase. -All schemas are generated from https://adcontextprotocol.org/schemas/v1/ +All schemas come from the official adcp-client-python library: +https://pypi.org/project/adcp/ """ -# Asset schemas -from ..schemas_generated._schemas_v1_core_assets_audio_asset_json import AudioAsset -from ..schemas_generated._schemas_v1_core_assets_css_asset_json import CssAsset -from ..schemas_generated._schemas_v1_core_assets_html_asset_json import HtmlAsset -from ..schemas_generated._schemas_v1_core_assets_image_asset_json import ImageAsset -from ..schemas_generated._schemas_v1_core_assets_javascript_asset_json import ( - JavascriptAsset as JavaScriptAsset, +# Core schemas from adcp library +from adcp.types.generated import ( + CreativeAsset as CreativeManifest, ) -from ..schemas_generated._schemas_v1_core_assets_promoted_offerings_asset_json import ( - PromotedOfferingsAsset, +from adcp.types.generated import ( + Format as CreativeFormat, ) -from ..schemas_generated._schemas_v1_core_assets_text_asset_json import TextAsset -from ..schemas_generated._schemas_v1_core_assets_url_asset_json import UrlAsset -from ..schemas_generated._schemas_v1_core_assets_video_asset_json import VideoAsset - -# Preview schemas (using AdCP creative asset as manifest base) -from ..schemas_generated._schemas_v1_core_creative_asset_json import CreativeAsset as CreativeManifest - -# Format schemas -from ..schemas_generated._schemas_v1_core_format_json import Format as CreativeFormat -from ..schemas_generated._schemas_v1_creative_list_creative_formats_response_json import ( - ListCreativeFormatsResponseCreativeAgent as ListCreativeFormatsResponse, +from adcp.types.generated import ( + ListCreativeFormatsResponse, ) # Build schemas (agent-specific, not part of AdCP) @@ -57,17 +45,12 @@ __all__ = [ "AssetReference", "AssetRequirement", - "AudioAsset", "BuildCreativeRequest", "BuildCreativeResponse", "CreativeFormat", "CreativeManifest", "CreativeOutput", - "CssAsset", "FormatRequirements", - "HtmlAsset", - "ImageAsset", - "JavaScriptAsset", "ListCreativeFormatsResponse", "PreviewContext", "PreviewCreativeRequest", @@ -77,8 +60,4 @@ "PreviewInput", "PreviewOptions", "PreviewVariant", - "PromotedOfferingsAsset", - "TextAsset", - "UrlAsset", - "VideoAsset", ] diff --git a/src/creative_agent/schemas/manifest.py b/src/creative_agent/schemas/manifest.py index b4287db..11f5107 100644 --- a/src/creative_agent/schemas/manifest.py +++ b/src/creative_agent/schemas/manifest.py @@ -2,10 +2,9 @@ from typing import Any +from adcp.types.generated import FormatId from pydantic import BaseModel -from ..schemas_generated._schemas_v1_core_format_json import FormatId - # CreativeManifest is imported from AdCP schemas via __init__.py # (uses CreativeAsset from AdCP as the base) diff --git a/src/creative_agent/schemas_generated/__init__.py b/src/creative_agent/schemas_generated/__init__.py deleted file mode 100644 index 554821d..0000000 --- a/src/creative_agent/schemas_generated/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Auto-generated Pydantic models from AdCP JSON schemas. - -⚠️ DO NOT EDIT FILES IN THIS DIRECTORY MANUALLY! - -Generated from: tests/schemas/v1/ -Generator: scripts/generate_schemas.py -Tool: datamodel-code-generator + custom $ref resolution - -To regenerate: - python scripts/generate_schemas.py - -Source: https://adcontextprotocol.org/schemas/v1/ -AdCP Version: v2.4 (schemas v1) -""" diff --git a/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py b/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py deleted file mode 100644 index 3c0e843..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py +++ /dev/null @@ -1,290 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_adagents_json.json - -from __future__ import annotations - -from enum import Enum -from typing import Annotated, Any, Optional - -from pydantic import ( - AnyUrl, - AwareDatetime, - BaseModel, - ConfigDict, - EmailStr, - Field, - RootModel, -) - - -class Contact(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - name: Annotated[ - str, - Field( - description="Name of the entity managing this file (e.g., 'Meta Advertising Operations', 'Clear Channel Digital')", - max_length=255, - min_length=1, - ), - ] - email: Annotated[ - Optional[EmailStr], - Field( - description="Contact email for questions or issues with this authorization file", - max_length=255, - min_length=1, - ), - ] = None - domain: Annotated[ - Optional[str], - Field( - description="Primary domain of the entity managing this file", - pattern="^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$", - ), - ] = None - seller_id: Annotated[ - Optional[str], - Field( - description="Seller ID from IAB Tech Lab sellers.json (if applicable)", - max_length=255, - min_length=1, - ), - ] = None - tag_id: Annotated[ - Optional[str], - Field( - description="TAG Certified Against Fraud ID for verification (if applicable)", - max_length=100, - min_length=1, - ), - ] = None - - -class PropertyType(Enum): - website = "website" - mobile_app = "mobile_app" - ctv_app = "ctv_app" - dooh = "dooh" - podcast = "podcast" - radio = "radio" - streaming_audio = "streaming_audio" - - -class Type(Enum): - domain = "domain" - subdomain = "subdomain" - network_id = "network_id" - ios_bundle = "ios_bundle" - android_package = "android_package" - apple_app_store_id = "apple_app_store_id" - google_play_id = "google_play_id" - roku_store_id = "roku_store_id" - fire_tv_asin = "fire_tv_asin" - samsung_app_id = "samsung_app_id" - apple_tv_bundle = "apple_tv_bundle" - bundle_id = "bundle_id" - venue_id = "venue_id" - screen_id = "screen_id" - openooh_venue_type = "openooh_venue_type" - rss_url = "rss_url" - apple_podcast_id = "apple_podcast_id" - spotify_show_id = "spotify_show_id" - podcast_guid = "podcast_guid" - - -class Identifier(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - type: Annotated[ - Type, - Field( - description="Valid identifier types for property identification across different media types", - examples=["domain", "ios_bundle", "venue_id", "apple_podcast_id"], - title="Property Identifier Types", - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')", - pattern="^[a-z0-9_]+$", - ), - ] - - -class Property(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - property_id: Annotated[ - Optional[str], - Field( - description="Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects. Recommended format: lowercase with underscores (e.g., 'cnn_ctv_app', 'instagram_mobile')", - pattern="^[a-z0-9_]+$", - ), - ] = None - property_type: Annotated[ - PropertyType, Field(description="Type of advertising property") - ] - name: Annotated[str, Field(description="Human-readable property name")] - identifiers: Annotated[ - list[Identifier], - Field(description="Array of identifiers for this property", min_length=1), - ] - tags: Annotated[ - Optional[list[Tag]], - Field( - description="Tags for categorization and grouping (e.g., network membership, content categories)" - ), - ] = None - publisher_domain: Annotated[ - Optional[str], - Field( - description="Domain where adagents.json should be checked for authorization validation. Required for list_authorized_properties response. Optional in adagents.json (file location implies domain)." - ), - ] = None - - -class Tags(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - name: Annotated[str, Field(description="Human-readable name for this tag")] - description: Annotated[ - str, Field(description="Description of what this tag represents") - ] - - -class PropertyId(RootModel[str]): - root: Annotated[str, Field(pattern="^[a-z0-9_]+$")] - - -class PropertyTag(PropertyId): - pass - - -class PublisherProperty(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the publisher's adagents.json is hosted (e.g., 'cnn.com')", - pattern="^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$", - ), - ] - property_ids: Annotated[ - Optional[list[PropertyId]], - Field( - description="Specific property IDs from the publisher's adagents.json properties array. Mutually exclusive with property_tags.", - min_length=1, - ), - ] = None - property_tags: Annotated[ - Optional[list[PropertyTag]], - Field( - description="Property tags from the publisher's adagents.json tags. Agent is authorized for all properties with these tags. Mutually exclusive with property_ids.", - min_length=1, - ), - ] = None - - -class AuthorizedAgent(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")] - authorized_for: Annotated[ - str, - Field( - description="Human-readable description of what this agent is authorized to sell", - max_length=500, - min_length=1, - ), - ] - property_ids: Annotated[ - Optional[list[PropertyId]], - Field( - description="Property IDs this agent is authorized for. Resolved against the top-level properties array in this file. Mutually exclusive with property_tags and properties fields.", - min_length=1, - ), - ] = None - property_tags: Annotated[ - Optional[list[PropertyTag]], - Field( - description="Tags identifying which properties this agent is authorized for. Resolved against the top-level properties array in this file using tag matching. Mutually exclusive with property_ids and properties fields.", - min_length=1, - ), - ] = None - properties: Annotated[ - Optional[list[Any]], - Field( - description="Specific properties this agent is authorized for (alternative to property_ids/property_tags). Mutually exclusive with property_ids and property_tags fields.", - min_length=1, - ), - ] = None - publisher_properties: Annotated[ - Optional[list[PublisherProperty]], - Field( - description="Properties from other publisher domains this agent is authorized for. Each entry specifies a publisher domain and which of their properties this agent can sell (by property_id or property_tags). Mutually exclusive with property_ids, property_tags, and properties fields.", - min_length=1, - ), - ] = None - - -class AuthorizedSalesAgents(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - field_schema: Annotated[ - Optional[str], - Field( - alias="$schema", - description="JSON Schema identifier for this adagents.json file", - ), - ] = "https://adcontextprotocol.org/schemas/v1/adagents.json" - contact: Annotated[ - Optional[Contact], - Field( - description="Contact information for the entity managing this adagents.json file (may be publisher or third-party operator)" - ), - ] = None - properties: Annotated[ - Optional[list[Property]], - Field( - description="Array of all properties covered by this adagents.json file. Same structure as list_authorized_properties response.", - min_length=1, - ), - ] = None - tags: Annotated[ - Optional[dict[str, Tags]], - Field( - description="Metadata for each tag referenced by properties. Same structure as list_authorized_properties response." - ), - ] = None - authorized_agents: Annotated[ - list[AuthorizedAgent], - Field( - description="Array of sales agents authorized to sell inventory for properties in this file", - min_length=1, - ), - ] - last_updated: Annotated[ - Optional[AwareDatetime], - Field( - description="ISO 8601 timestamp indicating when this file was last updated" - ), - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py deleted file mode 100644 index b89597a..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py +++ /dev/null @@ -1,24 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_audio-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Optional - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - - -class AudioAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[AnyUrl, Field(description="URL to the audio asset")] - duration_ms: Annotated[ - Optional[int], Field(description="Audio duration in milliseconds", ge=0) - ] = None - format: Annotated[ - Optional[str], Field(description="Audio file format (mp3, wav, aac, etc.)") - ] = None - bitrate_kbps: Annotated[ - Optional[int], Field(description="Audio bitrate in kilobits per second", ge=1) - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py deleted file mode 100644 index 6000cea..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py +++ /dev/null @@ -1,19 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_css-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class CssAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - content: Annotated[str, Field(description="CSS content")] - media: Annotated[ - Optional[str], - Field(description="CSS media query context (e.g., 'screen', 'print')"), - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_daast_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_daast_asset_json.py deleted file mode 100644 index 3140a0f..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_daast_asset_json.py +++ /dev/null @@ -1,86 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_daast-asset_json.json - -from __future__ import annotations - -from enum import Enum -from typing import Annotated, Optional, Union - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel - - -class DaastVersion(Enum): - field_1_0 = "1.0" - field_1_1 = "1.1" - - -class TrackingEvent(Enum): - start = "start" - first_quartile = "firstQuartile" - midpoint = "midpoint" - third_quartile = "thirdQuartile" - complete = "complete" - impression = "impression" - pause = "pause" - resume = "resume" - skip = "skip" - mute = "mute" - unmute = "unmute" - - -class DaastAsset1(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[AnyUrl, Field(description="URL endpoint that returns DAAST XML")] - content: Annotated[Optional[str], Field(description="Inline DAAST XML content")] = ( - None - ) - daast_version: Annotated[ - Optional[DaastVersion], Field(description="DAAST specification version") - ] = None - duration_ms: Annotated[ - Optional[int], - Field(description="Expected audio duration in milliseconds (if known)", ge=0), - ] = None - tracking_events: Annotated[ - Optional[list[TrackingEvent]], - Field(description="Tracking events supported by this DAAST tag"), - ] = None - companion_ads: Annotated[ - Optional[bool], Field(description="Whether companion display ads are included") - ] = None - - -class DaastAsset2(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[ - Optional[AnyUrl], Field(description="URL endpoint that returns DAAST XML") - ] = None - content: Annotated[str, Field(description="Inline DAAST XML content")] - daast_version: Annotated[ - Optional[DaastVersion], Field(description="DAAST specification version") - ] = None - duration_ms: Annotated[ - Optional[int], - Field(description="Expected audio duration in milliseconds (if known)", ge=0), - ] = None - tracking_events: Annotated[ - Optional[list[TrackingEvent]], - Field(description="Tracking events supported by this DAAST tag"), - ] = None - companion_ads: Annotated[ - Optional[bool], Field(description="Whether companion display ads are included") - ] = None - - -class DaastAsset(RootModel[Union[DaastAsset1, DaastAsset2]]): - root: Annotated[ - Union[DaastAsset1, DaastAsset2], - Field( - description="DAAST (Digital Audio Ad Serving Template) tag for third-party audio ad serving", - title="DAAST Asset", - ), - ] diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py deleted file mode 100644 index 0f2ff37..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_html-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class HtmlAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - content: Annotated[str, Field(description="HTML content")] - version: Annotated[ - Optional[str], Field(description="HTML version (e.g., 'HTML5')") - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py deleted file mode 100644 index 6d222bb..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py +++ /dev/null @@ -1,28 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_image-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Optional - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - - -class ImageAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[AnyUrl, Field(description="URL to the image asset")] - width: Annotated[ - Optional[int], Field(description="Image width in pixels", ge=1) - ] = None - height: Annotated[ - Optional[int], Field(description="Image height in pixels", ge=1) - ] = None - format: Annotated[ - Optional[str], - Field(description="Image file format (jpg, png, gif, webp, etc.)"), - ] = None - alt_text: Annotated[ - Optional[str], Field(description="Alternative text for accessibility") - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py deleted file mode 100644 index 1b22d15..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py +++ /dev/null @@ -1,25 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_javascript-asset_json.json - -from __future__ import annotations - -from enum import Enum -from typing import Annotated, Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class ModuleType(Enum): - esm = "esm" - commonjs = "commonjs" - script = "script" - - -class JavascriptAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - content: Annotated[str, Field(description="JavaScript content")] - module_type: Annotated[ - Optional[ModuleType], Field(description="JavaScript module type") - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py deleted file mode 100644 index 88f59dd..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py +++ /dev/null @@ -1,30 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_promoted-offerings-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Literal, Optional - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - - -class Colors(BaseModel): - primary: Optional[str] = None - secondary: Optional[str] = None - accent: Optional[str] = None - - -class PromotedOfferingsAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - asset_type: Literal["promoted_offerings"] - url: Annotated[ - Optional[AnyUrl], - Field( - description="URL of the advertiser's brand or offering (e.g., https://retailer.com)" - ), - ] = None - colors: Annotated[Optional[Colors], Field(description="Brand colors")] = None - fonts: Annotated[Optional[list[str]], Field(description="Brand fonts")] = None - tone: Annotated[Optional[str], Field(description="Brand tone/voice")] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py deleted file mode 100644 index 1c0634a..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py +++ /dev/null @@ -1,18 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_text-asset_json.json - -from __future__ import annotations - -from typing import Annotated, Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class TextAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - content: Annotated[str, Field(description="Text content")] - language: Annotated[ - Optional[str], Field(description="Language code (e.g., 'en', 'es', 'fr')") - ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py deleted file mode 100644 index d6c42c7..0000000 --- a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py +++ /dev/null @@ -1,31 +0,0 @@ -# generated by datamodel-codegen: -# filename: _schemas_v1_core_assets_url-asset_json.json - -from __future__ import annotations - -from enum import Enum -from typing import Annotated, Optional - -from pydantic import AnyUrl, BaseModel, ConfigDict, Field - - -class UrlType(Enum): - clickthrough = "clickthrough" - tracker_pixel = "tracker_pixel" - tracker_script = "tracker_script" - - -class UrlAsset(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - url: Annotated[AnyUrl, Field(description="URL reference")] - url_type: Annotated[ - Optional[UrlType], - Field( - description="Type of URL asset: 'clickthrough' for user click destination (landing page), 'tracker_pixel' for impression/event tracking via HTTP request (fires GET, expects pixel/204 response), 'tracker_script' for measurement SDKs that must load as