Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies = [
"boto3>=1.35.0",
"markdown>=3.6",
"bleach>=6.3.0",
"adcp>=2.18.0", # Official ADCP Python client with template format support
"adcp>=3.1.0", # Official ADCP Python client with template format support
]

[project.scripts]
Expand Down
8 changes: 5 additions & 3 deletions src/creative_agent/data/standard_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from typing import Any

from adcp import FormatCategory, FormatId
from adcp import FormatCategory, FormatId, get_required_assets
from adcp.types.generated_poc.core.format import Assets as LibAssets
from adcp.types.generated_poc.core.format import AssetsRequired as LibAssetsRequired
from adcp.types.generated_poc.core.format import Renders as LibRender
Expand Down Expand Up @@ -1716,8 +1716,10 @@ def has_asset_type(req: Any, target_type: AssetType | str) -> bool:
results = [
fmt
for fmt in results
if fmt.assets_required
and all(any(has_asset_type(req, asset_type) for req in fmt.assets_required) for asset_type in asset_types)
if get_required_assets(fmt)
and all(
any(has_asset_type(req, asset_type) for req in get_required_assets(fmt)) for asset_type in asset_types
)
]

return results
14 changes: 7 additions & 7 deletions src/creative_agent/renderers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ def build_asset_type_map(self, format_obj: Any) -> dict[str, str]:
Dictionary mapping asset_id to asset_type string
"""
asset_type_map = {}
if hasattr(format_obj, "assets_required") and format_obj.assets_required:
for required_asset in format_obj.assets_required:
if hasattr(format_obj, "assets") and format_obj.assets:
for asset in format_obj.assets:
# 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")
if isinstance(asset, dict):
asset_id = asset.get("asset_id")
asset_type = asset.get("asset_type")
else:
asset_id = getattr(required_asset, "asset_id", None)
asset_type = getattr(required_asset, "asset_type", None)
asset_id = getattr(asset, "asset_id", None)
asset_type = getattr(asset, "asset_type", None)

if asset_id and asset_type:
# Handle enum or string asset_type
Expand Down
16 changes: 6 additions & 10 deletions src/creative_agent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,16 +714,12 @@ def build_creative(
format_spec += f"Dimensions: {int(render.dimensions.width)}x{int(render.dimensions.height)}\n"

format_spec += "\nRequired Assets:\n"
if output_fmt.assets_required:
for asset_req in output_fmt.assets_required:
# assets_required are always Pydantic models (adcp 2.2.0+)
if hasattr(asset_req, "asset_group_id"):
# Repeatable group (AssetsRequired1)
format_spec += f"- {asset_req.asset_group_id} (repeatable group)\n"
elif hasattr(asset_req, "asset_id"):
# Individual asset (AssetsRequired)
asset_type = getattr(asset_req, "asset_type", "unknown")
format_spec += f"- {asset_req.asset_id} ({asset_type})\n"
required_assets = get_required_assets(output_fmt)
for asset_req in required_assets:
asset_id = getattr(asset_req, "asset_id", None)
asset_type = getattr(asset_req, "asset_type", "unknown")
if asset_id:
format_spec += f"- {asset_id} ({asset_type})\n"

# Add brand context if provided
brand_context = ""
Expand Down
20 changes: 11 additions & 9 deletions tests/integration/test_template_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import json

from adcp import FormatId, ListCreativeFormatsResponse
from adcp import FormatId, ListCreativeFormatsResponse, get_required_assets
from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter
from pydantic import AnyUrl

Expand Down Expand Up @@ -215,19 +215,20 @@ def test_get_video_template_by_base_id(self):
class TestTemplateAssetRequirements:
"""Test asset requirements for template vs concrete formats."""

def test_template_formats_have_assets_required(self):
"""Template formats should have assets_required defined."""
def test_template_formats_have_required_assets(self):
"""Template formats should have required assets defined."""
# Get display_image template
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_image")
fmt = get_format_by_id(format_id)

assert fmt is not None
assert fmt.assets_required is not None
assert len(fmt.assets_required) > 0
required_assets = get_required_assets(fmt)
assert required_assets is not None
assert len(required_assets) > 0

# Find the image asset
image_asset = None
for asset in fmt.assets_required:
for asset in required_assets:
if asset.asset_id == "banner_image":
image_asset = asset
break
Expand All @@ -241,12 +242,13 @@ def test_concrete_formats_have_explicit_requirements(self):
fmt = get_format_by_id(format_id)

assert fmt is not None
assert fmt.assets_required is not None
assert len(fmt.assets_required) > 0
required_assets = get_required_assets(fmt)
assert required_assets is not None
assert len(required_assets) > 0

# Find the image asset
image_asset = None
for asset in fmt.assets_required:
for asset in required_assets:
if asset.asset_id == "banner_image":
image_asset = asset
break
Expand Down
11 changes: 6 additions & 5 deletions tests/integration/test_tool_response_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
FormatId,
ListCreativeFormatsResponse,
PreviewCreativeResponse,
get_required_assets,
)

from creative_agent import server
Expand Down Expand Up @@ -118,16 +119,16 @@ def test_no_extra_wrapper_fields(self):
f"Response must have required keys {expected_keys}, got {actual_keys}"
)

def test_assets_required_have_asset_id(self):
"""Per ADCP PR #135, all AssetsRequired must have asset_id field."""
def test_assets_have_asset_id(self):
"""Per ADCP PR #135, all assets must have asset_id field."""
result = list_creative_formats()
response = ListCreativeFormatsResponse.model_validate(result.structured_content)

formats_with_assets = [fmt for fmt in response.formats if fmt.assets_required]
assert len(formats_with_assets) > 0, "Should have formats with assets_required"
formats_with_assets = [fmt for fmt in response.formats if get_required_assets(fmt)]
assert len(formats_with_assets) > 0, "Should have formats with required assets"

for fmt in formats_with_assets:
for asset in fmt.assets_required:
for asset in get_required_assets(fmt):
# Access asset_id - will raise AttributeError if missing
asset_dict = asset.model_dump() if hasattr(asset, "model_dump") else dict(asset)
assert "asset_id" in asset_dict, f"Format {fmt.format_id.id} has asset without asset_id: {asset_dict}"
Expand Down
42 changes: 23 additions & 19 deletions tests/unit/test_info_card_formats.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for info card format definitions."""

from adcp import FormatId
from adcp import FormatId, get_required_assets

from creative_agent.data.format_types import AssetType, Type
from creative_agent.data.standard_formats import AGENT_URL, INFO_CARD_FORMATS, filter_formats
Expand Down Expand Up @@ -95,12 +95,13 @@ def test_requires_product_assets(self):
assert fmt.assets
assert len(fmt.assets) == 9 # 8 original + impression_tracker

# Check required assets are in assets_required (backward compatibility)
assert fmt.assets_required
assert len(fmt.assets_required) == 3 # Only required=True assets
# Check required assets
required_assets = get_required_assets(fmt)
assert required_assets
assert len(required_assets) == 3 # Only required=True assets

# Check required asset ids in assets_required
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in fmt.assets_required}
# Check required asset ids
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in required_assets}
assert "product_image" in required_asset_ids
assert "product_name" in required_asset_ids
assert "product_description" in required_asset_ids
Expand All @@ -114,7 +115,7 @@ def test_requires_product_assets(self):
assert asset_type_map["product_description"] == "text"
assert asset_type_map["impression_tracker"] == "url"

# Check optional assets are in assets but not in assets_required
# Check optional assets are in assets but not required
optional_asset_ids = {
get_asset_attr(asset, "asset_id") for asset in fmt.assets if not get_asset_attr(asset, "required")
}
Expand Down Expand Up @@ -149,12 +150,13 @@ def test_requires_product_assets(self):
assert fmt.assets
assert len(fmt.assets) == 9 # 8 original + impression_tracker

# Check required assets are in assets_required (backward compatibility)
assert fmt.assets_required
assert len(fmt.assets_required) == 3 # Only required=True assets
# Check required assets
required_assets = get_required_assets(fmt)
assert required_assets
assert len(required_assets) == 3 # Only required=True assets

# Check required asset ids in assets_required
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in fmt.assets_required}
# Check required asset ids
required_asset_ids = {get_asset_attr(asset, "asset_id") for asset in required_assets}
assert "product_image" in required_asset_ids
assert "product_name" in required_asset_ids
assert "product_description" in required_asset_ids
Expand All @@ -168,7 +170,7 @@ def test_requires_product_assets(self):
assert asset_type_map["product_description"] == "text"
assert asset_type_map["impression_tracker"] == "url"

# Check optional assets are in assets but not in assets_required
# Check optional assets are in assets but not required
optional_asset_ids = {
get_asset_attr(asset, "asset_id") for asset in fmt.assets if not get_asset_attr(asset, "required")
}
Expand Down Expand Up @@ -198,9 +200,10 @@ def test_requires_format_asset(self):
format_id = FormatId(agent_url=AGENT_URL, id="format_card_standard")
results = filter_formats(format_ids=[format_id])
fmt = results[0]
assert fmt.assets_required
assert len(fmt.assets_required) == 1
asset = fmt.assets_required[0]
required_assets = get_required_assets(fmt)
assert required_assets
assert len(required_assets) == 1
asset = required_assets[0]
assert get_asset_attr(asset, "asset_id") == "format"
assert get_asset_attr(asset, "asset_type") == "text"
assert get_asset_attr(asset, "required") is True
Expand Down Expand Up @@ -228,9 +231,10 @@ def test_requires_format_asset(self):
format_id = FormatId(agent_url=AGENT_URL, id="format_card_detailed")
results = filter_formats(format_ids=[format_id])
fmt = results[0]
assert fmt.assets_required
assert len(fmt.assets_required) == 1
asset = fmt.assets_required[0]
required_assets = get_required_assets(fmt)
assert required_assets
assert len(required_assets) == 1
asset = required_assets[0]
assert get_asset_attr(asset, "asset_id") == "format"
assert get_asset_attr(asset, "asset_type") == "text"
assert get_asset_attr(asset, "required") is True
Expand Down
8 changes: 4 additions & 4 deletions tests/validation/test_template_parameter_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

import pytest
from adcp import FormatId
from adcp import FormatId, get_required_assets
from adcp.types.generated_poc.enums.format_id_parameter import FormatIdParameter
from pydantic import AnyUrl, ValidationError

Expand Down Expand Up @@ -112,7 +112,7 @@ def test_extract_dimensions_from_format_id(self):
fmt = get_format_by_id(template_format_id)

assert fmt is not None
assert fmt.assets_required is not None
assert get_required_assets(fmt) is not None

# Template format accepts dimensions parameter
assert FormatIdParameter.dimensions in getattr(fmt, "accepts_parameters", [])
Expand All @@ -131,7 +131,7 @@ def test_extract_duration_from_format_id(self):
fmt = get_format_by_id(template_format_id)

assert fmt is not None
assert fmt.assets_required is not None
assert get_required_assets(fmt) is not None

# Template format accepts duration parameter
assert FormatIdParameter.duration in getattr(fmt, "accepts_parameters", [])
Expand Down Expand Up @@ -161,7 +161,7 @@ def test_concrete_format_has_explicit_dimensions(self):
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_300x250_image")

fmt = get_format_by_id(format_id)
image_asset = next(a for a in fmt.assets_required if a.asset_id == "banner_image")
image_asset = next(a for a in get_required_assets(fmt) if a.asset_id == "banner_image")

requirements = getattr(image_asset, "requirements", {})

Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.