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.2.0", # Official ADCP Python client with semantic type aliases
"adcp>=2.12.0", # Official ADCP Python client with template format support
]

[project.scripts]
Expand Down
4 changes: 2 additions & 2 deletions src/creative_agent/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ 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 adcp import FormatId

from .data.standard_formats import AGENT_URL

Expand All @@ -77,7 +77,7 @@ 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 adcp import FormatId

from .data.standard_formats import AGENT_URL

Expand Down
327 changes: 248 additions & 79 deletions src/creative_agent/data/standard_formats.py

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions src/creative_agent/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,8 @@
"""

# Core schemas from adcp library
from adcp.types.generated import (
CreativeAsset as CreativeManifest,
)
from adcp.types.generated import (
Format as CreativeFormat,
)
from adcp.types.generated import (
ListCreativeFormatsResponse,
)
from adcp import CreativeManifest, ListCreativeFormatsResponse
from adcp import Format as CreativeFormat

# Build schemas (agent-specific, not part of AdCP)
from .build import (
Expand Down
2 changes: 1 addition & 1 deletion src/creative_agent/schemas/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Any

from adcp.types.generated import FormatId
from adcp import FormatId
from pydantic import BaseModel

# CreativeManifest is imported from AdCP schemas via __init__.py
Expand Down
7 changes: 4 additions & 3 deletions src/creative_agent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from datetime import UTC, datetime, timedelta
from typing import Any

from adcp.types.generated import FormatId
from adcp.types.generated_poc.list_creative_formats_response import Capability, CreativeAgent
from adcp import FormatId
from adcp.types import Capability
from adcp.types.generated_poc.media_buy.list_creative_formats_response import CreativeAgent
from fastmcp import FastMCP
from fastmcp.tools.tool import ToolResult
from mcp.types import TextContent
Expand Down Expand Up @@ -649,7 +650,7 @@ def build_creative(

if output_fmt.renders and len(output_fmt.renders) > 0:
render = output_fmt.renders[0]
if render.dimensions.width and render.dimensions.height:
if render.dimensions and render.dimensions.width and render.dimensions.height:
format_spec += f"Dimensions: {int(render.dimensions.width)}x{int(render.dimensions.height)}\n"

format_spec += "\nRequired Assets:\n"
Expand Down
26 changes: 2 additions & 24 deletions tests/integration/test_preview_creative.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from datetime import UTC, datetime

import pytest
from adcp.types.generated import FormatId
from adcp import FormatId
from pytest_mock import MockerFixture

from creative_agent import server
Expand All @@ -32,8 +32,6 @@ def test_preview_creative_with_spec_compliant_manifest(self, mock_s3_upload):
"""Test preview_creative tool with fully spec-compliant manifest."""
# Create spec-compliant Pydantic manifest
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -74,8 +72,6 @@ def test_preview_creative_with_spec_compliant_manifest(self, mock_s3_upload):
def test_preview_creative_with_custom_inputs(self, mock_s3_upload):
"""Test preview_creative with custom input variants."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -106,8 +102,6 @@ def test_preview_creative_validates_format_id_mismatch(self, mock_s3_upload):
"""Test that preview_creative rejects manifest with mismatched format_id."""
# Create manifest with DIFFERENT format_id than request
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_728x90_image"),
assets={
"banner_image": {
Expand All @@ -134,8 +128,6 @@ def test_preview_creative_validates_format_id_mismatch(self, mock_s3_upload):
def test_preview_creative_accepts_format_id_as_dict(self, mock_s3_upload):
"""Test that preview_creative accepts format_id as FormatId object (dict)."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -160,8 +152,6 @@ def test_preview_creative_accepts_format_id_as_dict(self, mock_s3_upload):
def test_preview_creative_validates_malicious_urls(self, mock_s3_upload):
"""Test that preview_creative validates and sanitizes malicious URLs."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -188,8 +178,6 @@ def test_preview_creative_validates_malicious_urls(self, mock_s3_upload):
def test_preview_creative_returns_interactive_url(self, mock_s3_upload):
"""Test that preview response includes interactive_url."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -213,8 +201,6 @@ def test_preview_creative_returns_interactive_url(self, mock_s3_upload):
def test_preview_creative_returns_expiration(self, mock_s3_upload):
"""Test that preview response includes expires_at timestamp."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -240,8 +226,6 @@ def test_preview_creative_returns_expiration(self, mock_s3_upload):
def test_preview_creative_rejects_unknown_format(self, mock_s3_upload):
"""Test that preview_creative rejects unknown format_id."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -264,11 +248,9 @@ def test_preview_creative_rejects_unknown_format(self, mock_s3_upload):

def test_preview_creative_returns_spec_compliant_response(self, mock_s3_upload):
"""Test that response matches ADCP PreviewCreativeResponse spec exactly."""
from adcp.types.generated import PreviewCreativeResponse
from adcp import PreviewCreativeResponse

manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -299,8 +281,6 @@ def test_preview_creative_returns_spec_compliant_response(self, mock_s3_upload):
def test_preview_expiration_is_valid_iso8601_timestamp(self, mock_s3_upload):
"""Test that expires_at is a valid ISO 8601 timestamp in the future."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -340,8 +320,6 @@ def test_preview_creative_fails_with_missing_required_asset(self, mock_s3_upload
"""Test that preview_creative returns clear error when required asset is missing."""
# Create manifest missing required click_url
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down
18 changes: 1 addition & 17 deletions tests/integration/test_preview_html_and_batch.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Integration tests for HTML output and batch preview modes."""

import pytest
from adcp.types.generated import FormatId, PreviewCreativeResponse
from adcp import FormatId, PreviewCreativeResponse

from creative_agent import server
from creative_agent.data.standard_formats import AGENT_URL
Expand All @@ -25,8 +25,6 @@ def mock_s3_upload(self, mocker):
def test_html_output_returns_preview_html(self):
"""Test that output_format='html' returns preview_html field."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -72,8 +70,6 @@ def test_html_output_returns_preview_html(self):
def test_html_output_does_not_upload_to_s3(self, mock_s3_upload):
"""Test that HTML output mode doesn't upload to S3."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -97,8 +93,6 @@ def test_html_output_does_not_upload_to_s3(self, mock_s3_upload):
def test_url_output_still_works(self, mock_s3_upload):
"""Test that default URL output mode still works."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -150,8 +144,6 @@ def mock_s3_upload(self, mocker):
def test_batch_mode_with_multiple_requests(self):
"""Test batch mode with multiple preview requests."""
manifest1 = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand All @@ -164,8 +156,6 @@ def test_batch_mode_with_multiple_requests(self):
)

manifest2 = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_728x90_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -211,8 +201,6 @@ def test_batch_mode_with_multiple_requests(self):
def test_batch_mode_with_html_output(self):
"""Test batch mode with HTML output format."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -252,8 +240,6 @@ def test_batch_mode_with_html_output(self):
def test_batch_mode_handles_errors_gracefully(self):
"""Test that batch mode handles individual request errors."""
valid_manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down Expand Up @@ -295,8 +281,6 @@ def test_batch_mode_handles_errors_gracefully(self):
def test_batch_mode_per_request_output_format_override(self):
"""Test that individual requests can override batch output_format."""
manifest = CreativeManifest(
creative_id="test-creative",
name="Test Creative",
format_id=FormatId(agent_url=AGENT_URL, id="display_300x250_image"),
assets={
"banner_image": {
Expand Down
Loading