diff --git a/README.md b/README.md index 797309b5..bd833d48 100644 --- a/README.md +++ b/README.md @@ -466,12 +466,183 @@ All AdCP tools with full type safety: - `list_creatives()` - List creative assets - `get_media_buy_delivery()` - Get delivery performance +**Creative Management:** +- `preview_creative()` - Preview creative before building +- `build_creative()` - Generate production-ready creative assets + **Audience & Targeting:** - `list_authorized_properties()` - Get authorized properties - `get_signals()` - Get audience signals - `activate_signal()` - Activate audience signals - `provide_performance_feedback()` - Send performance feedback +## Workflow Examples + +### Complete Media Buy Workflow + +A typical media buy workflow involves discovering products, creating the buy, and managing creatives: + +```python +from adcp import ADCPClient, AgentConfig, GetProductsRequest, CreateMediaBuyRequest +from adcp import BrandManifest, PublisherPropertiesAll + +# 1. Connect to agent +config = AgentConfig(id="sales_agent", agent_uri="https://...", protocol="mcp") +async with ADCPClient(config) as client: + + # 2. Discover available products + products_result = await client.get_products( + GetProductsRequest(brief="Premium video inventory for coffee brand") + ) + + if products_result.success: + product = products_result.data.products[0] + print(f"Found product: {product.name}") + + # 3. Create media buy reservation + media_buy_result = await client.create_media_buy( + CreateMediaBuyRequest( + brand_manifest=BrandManifest( + name="Coffee Co", + brand_url="https://coffeeco.com", + logo_url="https://coffeeco.com/logo.png", + # ... additional brand details + ), + packages=[{ + "package_id": product.packages[0].package_id, + "quantity": 1000000 # impressions + }], + publisher_properties=PublisherPropertiesAll( + selection_type="all" # Target all authorized properties + ) + ) + ) + + if media_buy_result.success: + media_buy_id = media_buy_result.data.media_buy_id + print(f"✅ Media buy created: {media_buy_id}") + + # 4. Update media buy if needed + from adcp import UpdateMediaBuyPackagesRequest + + update_result = await client.update_media_buy( + UpdateMediaBuyPackagesRequest( + media_buy_id=media_buy_id, + packages=[{ + "package_id": product.packages[0].package_id, + "quantity": 1500000 # Increase budget + }] + ) + ) + + if update_result.success: + print("✅ Media buy updated") +``` + +### Complete Creative Workflow + +Build and deliver production-ready creatives: + +```python +from adcp import ADCPClient, AgentConfig +from adcp import PreviewCreativeFormatRequest, BuildCreativeRequest +from adcp import CreativeManifest, PlatformDeployment + +# 1. Connect to creative agent +config = AgentConfig(id="creative_agent", agent_uri="https://...", protocol="mcp") +async with ADCPClient(config) as client: + + # 2. List available formats + formats_result = await client.list_creative_formats() + + if formats_result.success: + format_id = formats_result.data.formats[0].format_id + print(f"Using format: {format_id.id}") + + # 3. Preview creative (test before building) + preview_result = await client.preview_creative( + PreviewCreativeFormatRequest( + target_format_id=format_id.id, + inputs={ + "headline": "Fresh Coffee Daily", + "cta": "Order Now" + }, + output_format="url" # Get preview URL + ) + ) + + if preview_result.success: + preview_url = preview_result.data.renders[0].url + print(f"Preview at: {preview_url}") + + # 4. Build production creative + build_result = await client.build_creative( + BuildCreativeRequest( + manifest=CreativeManifest( + format_id=format_id, + brand_url="https://coffeeco.com", + # ... creative content + ), + target_format_id=format_id.id, + deployment=PlatformDeployment( + type="platform", + platform_id="google_admanager" + ) + ) + ) + + if build_result.success: + vast_url = build_result.data.assets[0].url + print(f"✅ Creative ready: {vast_url}") +``` + +### Integrated Workflow: Media Buy + Creatives + +Combine both workflows for a complete campaign setup: + +```python +from adcp import ADCPMultiAgentClient, AgentConfig +from adcp import GetProductsRequest, CreateMediaBuyRequest, BuildCreativeRequest + +# Connect to both sales and creative agents +async with ADCPMultiAgentClient( + agents=[ + AgentConfig(id="sales", agent_uri="https://sales-agent.com", protocol="mcp"), + AgentConfig(id="creative", agent_uri="https://creative-agent.com", protocol="mcp"), + ] +) as client: + + # 1. Get products from sales agent + sales_agent = client.agent("sales") + products = await sales_agent.simple.get_products( + brief="Premium video inventory" + ) + + # 2. Get creative formats from creative agent + creative_agent = client.agent("creative") + formats = await creative_agent.simple.list_creative_formats() + + # 3. Build creative asset + creative_result = await creative_agent.build_creative( + BuildCreativeRequest( + manifest=creative_manifest, + target_format_id=formats.formats[0].format_id.id + ) + ) + + # 4. Create media buy with creative + media_buy_result = await sales_agent.create_media_buy( + CreateMediaBuyRequest( + brand_manifest=brand_manifest, + packages=[{"package_id": products.products[0].packages[0].package_id}], + publisher_properties=publisher_properties, + creative_urls=[creative_result.data.assets[0].url] + ) + ) + + print(f"✅ Campaign live: {media_buy_result.data.media_buy_id}") +``` + ## Property Discovery (AdCP v2.2.0) Build agent registries by discovering properties agents can sell: diff --git a/schemas/cache/.hashes.json b/schemas/cache/.hashes.json index abd7830f..d2f60d4d 100644 --- a/schemas/cache/.hashes.json +++ b/schemas/cache/.hashes.json @@ -1,8 +1,7 @@ { - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/index.json": "46f68f0e55d63361c0eda1cc7ffff4f25c9416467dd3a04db2cca0973f086b7d", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/index.json": "3d58e9a3c220f37b9245c8488f2d12291b787972dc3d069a5dd9f1ddcb2c377b", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/adagents.json": "f32e3778a454b7ae65f42952906f06466008bfd1208b83729e14d5f2164902d5", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/activation-key.json": "bb9c20c6200b651ce6db89f7160be60b9845dbbb4390a13363ea5b82c1c3c786", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/asset-type.json": "a61ac8e14a61adef64d10d6ab39c204d703c087f29efa45c69c527988c93cd3d", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/audio-asset.json": "e25d688d22c8b130d9d1a2a09f9aa462cb7a5c83db6eea6f328cd937f8625a3f", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/css-asset.json": "e0a3e3b668f564cf804d0b893833188203b8992cd5d118e4fd3d19c85cb501a1", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/daast-asset.json": "9a4e469cf7d60b6ed31d250f669e5383161c55f5235b14fced4382c9374877b6", @@ -16,17 +15,17 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/video-asset.json": "24e2e69c25e67ce48e5aaf9e3367b37796d0969530f1dfea93c36ff8194ebe6c", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/assets/webhook-asset.json": "ee8fe23b17b4150f02c13f5461b8d4618a041a345c48a356904c666078caf72d", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/brand-manifest-ref.json": "bf564ec2a537f7c931d52244a20549c493f151eda264de7487aa2d8bc25e184c", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/brand-manifest.json": "08f881d2435d5718a05573899fe12aa53fe0626c6808c0f1481581445b36d20c", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/brand-manifest.json": "5b34ba67376c2ee38ea9221ebaf8ed825b98ca8089ef5b794cc8a7687f089e54", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/creative-asset.json": "f49b85b1fc82878e3d0227eda735fba3a4eb39008070a6ab2b9ceacea1c2c0db", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/creative-assignment.json": "1319ea89aedd48b5d354fa4b18668e2bb16a6e7e0ce640cdea63e55d3abf7941", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/creative-manifest.json": "160d56152c35f56dc9a26b6906e7e1f9d0e80826d25a006aba56487fa627e1eb", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/creative-policy.json": "f65903eae4ccf16b8c738be36b74edb31101698c30bf7e167a9c73c2a7417444", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/delivery-metrics.json": "4a2c3c6b684668d47eb954132ecc522dde989cec5e74bd9acaa10306b9e72d68", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/deployment.json": "ed9081ab01fa591d64d84fcb85e6fec796668e7c5c84cbf41394ca8c0120b231", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/destination.json": "6288199198075a827b4669a2964dc3c2cf3228195503506ab3bf5f637baee76f", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/deployment.json": "7681b552be408d132cbbf431a09a49f06b34fb7c535fb8cece2ff80097c6e708", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/destination.json": "26dfdaae419537512e4fbe961317fd7386e76c2d33d6945ef43e00e074df8e17", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/error.json": "8ef7f825f398f28f63c3ec6a1631e49cd1fe08034d2e4c24b5153cad3e75e389", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/format-id.json": "13811721d182f84d2bfd28670c4ecc85cd15c0392a57ababd9d87fe7befd6b13", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/format.json": "04812788375e4f1af4ec3feb9766869cd3bc77381e51c8ab8ecc26a1cc19e870", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/format.json": "bc3980756be32803fca4c293dfe17db8c3c75897e9c74d3e48b10e2c78c15466", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/frequency-cap.json": "143c0cd68fec7247d9633c13fb1f6edf2f18756694e8a29f60a6c5c672e918c9", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/measurement.json": "c3cf5e4026cf1ef93ea3a145ebfa11a945927a556266d1ca9a78a0b53bbcdac1", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/media-buy.json": "20d4cb99b9f290856769cfe1e0ec2d198f68cace214fe3e6e6ea7f84d8b74780", @@ -50,16 +49,18 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/tasks-get-response.json": "551f9478f725d7a98f48a49be9c6a3088cc6e06fa1585aeb30e3085d85f9cfc5", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/tasks-list-request.json": "94048fb5a7eecdef061898a5a12c4a17bc794da2b4423d66c86ddeb6c3abf6c0", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/tasks-list-response.json": "529f093524948dc020fcfa3b6aaf9bf3f3fd5f4bec707e286350f86f4caea052", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/webhook-payload.json": "2bac25b40f5ea1f36d5f1adbbba4f5bac3b86e9ec5687426d5e10ad2b212ffc7", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/core/webhook-payload.json": "2a985b19a4ea232295a7e731dcb9318b435d5f45dd182baea893f4c75d3d2230", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/asset-types/index.json": "6bdcf5d78558ab1ff75759ed7b0b1271daab828e2cd92e0e51154b7759e72fd9", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/list-creative-formats-request.json": "0bc06b927aa7c1c48e49391232153a6dcdc978eb1df42f86837e1ce31e3ff9c2", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/list-creative-formats-response.json": "f897a1358b0b0ac61d6bb3c99323f9b25f473278a79ee0ac10f382c031be4ff4", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/preview-creative-request.json": "db8553c8c7f7194b87981a538b1f62a7b91e12bef732b34d6817cb05a523ca4b", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/preview-creative-response.json": "361d824a0abe6c42c21bc1778ebc35dabf68dcb99b5b0e1e6ff8dfae4b319b05", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/creative/preview-render.json": "d9312c330425552f6f8aff32e9fb3d792face83141b9b62dbf79a4efac712c41", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/asset-content-type.json": "94815898f629581d8dba307808538584c49a2803cfce545f73cefeee742d825c", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/channels.json": "e3f7d6ca073a48ed156fe1a00a0f2772415743823aee877dedf7b0db183c58a7", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/creative-status.json": "193e3ab2d6c6271c83bf48f19b39574600407767aac3e9b71dc6c5ef49c99f95", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/delivery-type.json": "4af6c09902eb7c74727a60ca28cdd2f5aec10c3b59d2c5b864fe8f6f83f07752", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/format-category.json": "90924784874e5be4c9ef4180d0a8e2100e6e6db362cd5a3a98988e56d14f56db", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/frequency-cap-scope.json": "a243a31fa69803d6a6c2680f5465969f0d14ec79b6c0ac4ed89c55379a0d782e", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/identifier-types.json": "e9fedddebf8d09857b77094c36de32c619b7e47c667791a5d8ec837c2e672500", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/enums/media-buy-status.json": "a459b9007750a4ce2151a09c82478179311543e17c3aef49d15c3b7b77c5029c", @@ -73,14 +74,14 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/build-creative-request.json": "1356a721f29a5e82e144640183c57df24c4c68eab35f7f4e6e8c6e93a8c10499", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/build-creative-response.json": "89258fb28bedf6b95c9f6ee8810b0674965f1c49746376823c6fedc9b2634ad2", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/create-media-buy-request.json": "69abc79214995c77c590b24fe6ad7cd9c322b7651aa9e3c065c9c39cdeeac656", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/create-media-buy-response.json": "5f00b2f6554188cec7c5feb47674aa2ff1fddfb44162c5fb84f75368b1faebe7", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/create-media-buy-response.json": "75e06a480a44575443d6ca0a14960c77725cfa9f878429e24dd09b1b151db0af", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/get-media-buy-delivery-request.json": "20464f0fa84ce3bed82ff18eeb8b5b3b46745a51e11e2393df58c0f309bf2cc6", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/get-media-buy-delivery-response.json": "89f62b754d9d626ec9068a7b030808dc3eba316cbb9c86369a4a68a5b40d38f3", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/get-products-request.json": "5d01c326361c21ed0735ffaada036e3017ac75c318832d8607cd935ecaaa4698", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/get-products-response.json": "ddc64c84fa4d4aec43981927bf5d2d36130ed9ae5aa97bda48924f63e17752f4", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-authorized-properties-request.json": "b9b8bfbd7d549506f11cf46781f269402cdb72d19562f53643fd612fccd8dc16", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-authorized-properties-response.json": "0f4ec18c9e3fe5b3a9288177134df58deab44bdc5fcc61a0838e93ceefebbcd8", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-creative-formats-request.json": "8d48f0391f3d6a359aca61cbb0389bb127be3d745dd7a9aabdac1853a6233a0f", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-creative-formats-request.json": "e10a8431c46d8020b23d0daa5caad844fa21f52efdd99e4c38f878aee26d8646", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-creative-formats-response.json": "30ec1737d5b51f3d6920af5cc0a081efe09ba7b4e635eda26a45ca6be218e18d", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-creatives-request.json": "949b713c4b2a11f3230f1696694542b020547dfa1841a36ed46f009bd5559d5b", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/list-creatives-response.json": "8539e1d24b4671c9f1d3ee0f7aed247c832ada85eec23dece7041302571f2409", @@ -90,7 +91,7 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/sync-creatives-request.json": "42ad6402161e0528b4ea1b6e75083437f200ce064ac2acef2adb57c0867fc658", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/sync-creatives-response.json": "c06c1296e4ddea110b8dee1b0e40289fea32f52c56ebf123512319008dfcc615", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/update-media-buy-request.json": "e907faefc302a58870554f8a1ac84de5529e4806c4da04879f153cade2372747", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/update-media-buy-response.json": "b4214297b1f975c8c7f2f18149ffa41e599237d8619b82796a9d7a36bb3c4593", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/media-buy/update-media-buy-response.json": "643562faab311e237095d024cd1d4383b01effd3d9bff5af412837d9b85522b4", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/cpc-option.json": "8db2f8f68bff3e9f5c8c8a0843e307e7b40533883d14bab210c78ff73dd2de63", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/cpcv-option.json": "df1a5e0101397d0a5fe72f3bbb591794a6c1936d7a21db6ccc4986e13d822bc5", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/cpm-auction-option.json": "55af0a07ab1e8ad56963d3fdba3b7ca302a764022e849929748752bb9090850c", @@ -100,8 +101,8 @@ "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/flat-rate-option.json": "133ebe6814dacb72989d79da945437321baabdf85a3e5b14c2a3b695ee471bee", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/vcpm-auction-option.json": "67aa8d6695991dc65f9abc9a126e82a031a5fabc7a611f8d29699c2fd5e38c82", "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/pricing-options/vcpm-fixed-option.json": "b560ed8eb0196a793d6d1304a4dea62dc3a1fd06b1fb676b95aa93b68618fce9", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/activate-signal-request.json": "6d60816d28aa28d188b9180c909e089954eca6b3dc734bd315d639ff345a9679", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/activate-signal-response.json": "90e3b5875040b3ba3d7d0f2a1279cf9c7ab5015d0dfa2fd0849cf38f734bd5b3", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/get-signals-request.json": "38f802f555aa3df77ebbb0e48f2bd93ae15571a2bbb3982bd2ee7e73a19451d5", - "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/get-signals-response.json": "e23e1fcfeff0edef4b8a1d47d594b206acb51b9377d30e77fb2d190992638076" + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/activate-signal-request.json": "bb4d0e0e3e767856f8541488d2f847161b6f6bfc620d7d98efad6b76558811cc", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/activate-signal-response.json": "efb5ed2da9088c421f0a8b4c2582e98e44a0d015917c0e8c1d172ef5330206a1", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/get-signals-request.json": "5284a89c1b36f5ff17e0c638b5594b644eaf53e5c53b60dce58a7561025ba2c7", + "https://raw.githubusercontent.com/adcontextprotocol/adcp/main/static/schemas/v1/signals/get-signals-response.json": "28bf87189c808b1a5c3263e7b53a99407f9bf50e140974cdfc5feac582b9fe1b" } \ No newline at end of file diff --git a/schemas/cache/1.0.0/activate-signal-request.json b/schemas/cache/1.0.0/activate-signal-request.json index fb57a85e..38d016c8 100644 --- a/schemas/cache/1.0.0/activate-signal-request.json +++ b/schemas/cache/1.0.0/activate-signal-request.json @@ -2,17 +2,17 @@ "$id": "/schemas/v1/signals/activate-signal-request.json", "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, - "description": "Request parameters for activating a signal on a specific destination", + "description": "Request parameters for activating a signal on a specific deployment target", "properties": { "context": { "additionalProperties": true, "description": "Initiator-provided context included in the request payload. Agents must echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", "type": "object" }, - "destinations": { - "description": "Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.", + "deployments": { + "description": "Target deployment(s) for activation. If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.", "items": { - "$ref": "destination.json" + "$ref": "/schemas/v1/core/destination.json" }, "minItems": 1, "type": "array" @@ -24,7 +24,7 @@ }, "required": [ "signal_agent_segment_id", - "destinations" + "deployments" ], "title": "Activate Signal Request", "type": "object" diff --git a/schemas/cache/1.0.0/activate-signal-response.json b/schemas/cache/1.0.0/activate-signal-response.json index ef19709a..02e982b7 100644 --- a/schemas/cache/1.0.0/activate-signal-response.json +++ b/schemas/cache/1.0.0/activate-signal-response.json @@ -5,7 +5,7 @@ "oneOf": [ { "additionalProperties": false, - "description": "Success response - signal activated successfully to one or more destinations", + "description": "Success response - signal activated successfully to one or more deployment targets", "not": { "required": [ "errors" @@ -18,9 +18,9 @@ "type": "object" }, "deployments": { - "description": "Array of deployment results for each destination", + "description": "Array of deployment results for each deployment target", "items": { - "$ref": "deployment.json" + "$ref": "/schemas/v1/core/deployment.json" }, "type": "array" } @@ -47,7 +47,7 @@ "errors": { "description": "Array of errors explaining why activation failed (e.g., platform connectivity issues, signal definition problems, authentication failures)", "items": { - "$ref": "error.json" + "$ref": "/schemas/v1/core/error.json" }, "minItems": 1, "type": "array" diff --git a/schemas/cache/1.0.0/asset-content-type.json b/schemas/cache/1.0.0/asset-content-type.json new file mode 100644 index 00000000..b9075c39 --- /dev/null +++ b/schemas/cache/1.0.0/asset-content-type.json @@ -0,0 +1,22 @@ +{ + "$id": "/schemas/v1/enums/asset-content-type.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Types of content that can be used as creative assets. Describes what KIND of content an asset contains (image, video, code, etc.), not where it displays.", + "enum": [ + "image", + "video", + "audio", + "text", + "markdown", + "html", + "css", + "javascript", + "vast", + "daast", + "promoted_offerings", + "url", + "webhook" + ], + "title": "Asset Content Type", + "type": "string" +} \ No newline at end of file diff --git a/schemas/cache/1.0.0/brand-manifest.json b/schemas/cache/1.0.0/brand-manifest.json index 053f6011..e88953fb 100644 --- a/schemas/cache/1.0.0/brand-manifest.json +++ b/schemas/cache/1.0.0/brand-manifest.json @@ -125,14 +125,8 @@ "type": "string" }, "asset_type": { - "description": "Type of asset", - "enum": [ - "image", - "video", - "audio", - "text" - ], - "type": "string" + "$ref": "/schemas/v1/enums/asset-content-type.json", + "description": "Type of asset. Note: Brand manifests typically contain basic media assets (image, video, audio, text). Code assets (html, javascript, css) and ad markup (vast, daast) are usually not part of brand asset libraries." }, "description": { "description": "Asset description or usage notes", diff --git a/schemas/cache/1.0.0/create-media-buy-response.json b/schemas/cache/1.0.0/create-media-buy-response.json index 51c6888d..3013fd76 100644 --- a/schemas/cache/1.0.0/create-media-buy-response.json +++ b/schemas/cache/1.0.0/create-media-buy-response.json @@ -31,24 +31,9 @@ "type": "string" }, "packages": { - "description": "Array of created packages", + "description": "Array of created packages with complete state information", "items": { - "additionalProperties": false, - "properties": { - "buyer_ref": { - "description": "Buyer's reference identifier for the package", - "type": "string" - }, - "package_id": { - "description": "Publisher's unique identifier for the package", - "type": "string" - } - }, - "required": [ - "package_id", - "buyer_ref" - ], - "type": "object" + "$ref": "/schemas/v1/core/package.json" }, "type": "array" } @@ -91,7 +76,7 @@ "errors": { "description": "Array of errors explaining why the operation failed", "items": { - "$ref": "error.json" + "$ref": "/schemas/v1/core/error.json" }, "minItems": 1, "type": "array" diff --git a/schemas/cache/1.0.0/deployment.json b/schemas/cache/1.0.0/deployment.json index feb8cf82..f3dd8478 100644 --- a/schemas/cache/1.0.0/deployment.json +++ b/schemas/cache/1.0.0/deployment.json @@ -1,7 +1,7 @@ { "$id": "/schemas/v1/core/deployment.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A signal deployment to a specific destination platform with activation status and key", + "description": "A signal deployment to a specific deployment target with activation status and key", "oneOf": [ { "additionalProperties": false, @@ -11,8 +11,8 @@ "type": "string" }, "activation_key": { - "$ref": "activation-key.json", - "description": "The key to use for targeting. Only present if is_live=true AND requester has access to this destination." + "$ref": "/schemas/v1/core/activation-key.json", + "description": "The key to use for targeting. Only present if is_live=true AND requester has access to this deployment." }, "deployed_at": { "description": "Timestamp when activation completed (if is_live=true)", @@ -25,7 +25,7 @@ "type": "number" }, "is_live": { - "description": "Whether signal is currently active on this destination", + "description": "Whether signal is currently active on this deployment", "type": "boolean" }, "platform": { @@ -53,11 +53,11 @@ "type": "string" }, "activation_key": { - "$ref": "activation-key.json", - "description": "The key to use for targeting. Only present if is_live=true AND requester has access to this destination." + "$ref": "/schemas/v1/core/activation-key.json", + "description": "The key to use for targeting. Only present if is_live=true AND requester has access to this deployment." }, "agent_url": { - "description": "URL identifying the destination agent", + "description": "URL identifying the deployment agent", "format": "uri", "type": "string" }, @@ -72,7 +72,7 @@ "type": "number" }, "is_live": { - "description": "Whether signal is currently active on this destination", + "description": "Whether signal is currently active on this deployment", "type": "boolean" }, "type": { diff --git a/schemas/cache/1.0.0/destination.json b/schemas/cache/1.0.0/destination.json index 535c4806..177ee84f 100644 --- a/schemas/cache/1.0.0/destination.json +++ b/schemas/cache/1.0.0/destination.json @@ -1,7 +1,7 @@ { "$id": "/schemas/v1/core/destination.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A destination platform where signals can be activated (DSP, sales agent, etc.)", + "description": "A deployment target where signals can be activated (DSP, sales agent, etc.)", "oneOf": [ { "additionalProperties": false, @@ -16,7 +16,7 @@ }, "type": { "const": "platform", - "description": "Discriminator indicating this is a platform-based destination" + "description": "Discriminator indicating this is a platform-based deployment" } }, "required": [ @@ -33,13 +33,13 @@ "type": "string" }, "agent_url": { - "description": "URL identifying the destination agent (for sales agents, etc.)", + "description": "URL identifying the deployment agent (for sales agents, etc.)", "format": "uri", "type": "string" }, "type": { "const": "agent", - "description": "Discriminator indicating this is an agent URL-based destination" + "description": "Discriminator indicating this is an agent URL-based deployment" } }, "required": [ diff --git a/schemas/cache/1.0.0/format-category.json b/schemas/cache/1.0.0/format-category.json new file mode 100644 index 00000000..e3523d44 --- /dev/null +++ b/schemas/cache/1.0.0/format-category.json @@ -0,0 +1,16 @@ +{ + "$id": "/schemas/v1/enums/format-category.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "High-level categories for creative formats based on media type and delivery channel. Describes WHERE and HOW a creative displays, not what content it contains.", + "enum": [ + "audio", + "video", + "display", + "native", + "dooh", + "rich_media", + "universal" + ], + "title": "Format Category", + "type": "string" +} \ No newline at end of file diff --git a/schemas/cache/1.0.0/format.json b/schemas/cache/1.0.0/format.json index 61dec25b..03215ac7 100644 --- a/schemas/cache/1.0.0/format.json +++ b/schemas/cache/1.0.0/format.json @@ -20,23 +20,8 @@ "type": "string" }, "asset_type": { - "description": "Type of asset", - "enum": [ - "image", - "video", - "audio", - "vast", - "daast", - "text", - "markdown", - "html", - "css", - "javascript", - "url", - "webhook", - "promoted_offerings" - ], - "type": "string" + "$ref": "/schemas/v1/enums/asset-content-type.json", + "description": "Type of asset" }, "item_type": { "const": "individual", @@ -80,23 +65,8 @@ "type": "string" }, "asset_type": { - "description": "Type of asset", - "enum": [ - "image", - "video", - "audio", - "vast", - "daast", - "text", - "markdown", - "html", - "css", - "javascript", - "url", - "webhook", - "promoted_offerings" - ], - "type": "string" + "$ref": "/schemas/v1/enums/asset-content-type.json", + "description": "Type of asset" }, "required": { "description": "Whether this asset is required in each repetition", @@ -164,7 +134,7 @@ "description": "Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.", "properties": { "format_id": { - "$ref": "format-id.json", + "$ref": "/schemas/v1/core/format-id.json", "description": "Creative format defining the card layout (typically format_card_standard)" }, "manifest": { @@ -184,7 +154,7 @@ "description": "Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.", "properties": { "format_id": { - "$ref": "format-id.json", + "$ref": "/schemas/v1/core/format-id.json", "description": "Creative format defining the detailed card layout (typically format_card_detailed)" }, "manifest": { @@ -200,7 +170,7 @@ "type": "object" }, "format_id": { - "$ref": "format-id.json", + "$ref": "/schemas/v1/core/format-id.json", "description": "Structured format identifier with agent URL and format name" }, "name": { @@ -210,7 +180,7 @@ "output_format_ids": { "description": "For generative formats: array of format IDs that this format can generate. When a format accepts inputs like brand_manifest and message, this specifies what concrete output formats can be produced (e.g., a generative banner format might output standard image banner formats).", "items": { - "$ref": "format-id.json" + "$ref": "/schemas/v1/core/format-id.json" }, "type": "array" }, @@ -316,17 +286,8 @@ "type": "array" }, "type": { - "description": "Media type of this format - determines rendering method and asset requirements", - "enum": [ - "audio", - "video", - "display", - "native", - "dooh", - "rich_media", - "universal" - ], - "type": "string" + "$ref": "/schemas/v1/enums/format-category.json", + "description": "Media type of this format - determines rendering method and asset requirements" } }, "required": [ diff --git a/schemas/cache/1.0.0/get-signals-request.json b/schemas/cache/1.0.0/get-signals-request.json index 82b75114..2982a57c 100644 --- a/schemas/cache/1.0.0/get-signals-request.json +++ b/schemas/cache/1.0.0/get-signals-request.json @@ -11,7 +11,7 @@ }, "deliver_to": { "additionalProperties": false, - "description": "Destination platforms where signals need to be activated", + "description": "Deployment targets where signals need to be activated", "properties": { "countries": { "description": "Countries where signals will be used (ISO codes)", @@ -21,17 +21,17 @@ }, "type": "array" }, - "destinations": { - "description": "List of destination platforms (DSPs, sales agents, etc.). If the authenticated caller matches one of these destinations, activation keys will be included in the response.", + "deployments": { + "description": "List of deployment targets (DSPs, sales agents, etc.). If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.", "items": { - "$ref": "destination.json" + "$ref": "/schemas/v1/core/destination.json" }, "minItems": 1, "type": "array" } }, "required": [ - "destinations", + "deployments", "countries" ], "type": "object" diff --git a/schemas/cache/1.0.0/get-signals-response.json b/schemas/cache/1.0.0/get-signals-response.json index cbf6212b..90266056 100644 --- a/schemas/cache/1.0.0/get-signals-response.json +++ b/schemas/cache/1.0.0/get-signals-response.json @@ -12,7 +12,7 @@ "errors": { "description": "Task-specific errors and warnings (e.g., signal discovery or pricing issues)", "items": { - "$ref": "error.json" + "$ref": "/schemas/v1/core/error.json" }, "type": "array" }, @@ -32,9 +32,9 @@ "type": "string" }, "deployments": { - "description": "Array of destination deployments", + "description": "Array of deployment targets", "items": { - "$ref": "deployment.json" + "$ref": "/schemas/v1/core/deployment.json" }, "type": "array" }, diff --git a/schemas/cache/1.0.0/index.json b/schemas/cache/1.0.0/index.json index 03c248d4..74c8acb3 100644 --- a/schemas/cache/1.0.0/index.json +++ b/schemas/cache/1.0.0/index.json @@ -210,6 +210,10 @@ "enums": { "description": "Enumerated types and constants", "schemas": { + "asset-content-type": { + "$ref": "/schemas/v1/enums/asset-content-type.json", + "description": "Types of content that can be used as creative assets (image, video, html, etc.)" + }, "channels": { "$ref": "/schemas/v1/enums/channels.json", "description": "Advertising channels (display, video, dooh, ctv, audio, etc.)" @@ -222,6 +226,10 @@ "$ref": "/schemas/v1/enums/delivery-type.json", "description": "Type of inventory delivery" }, + "format-category": { + "$ref": "/schemas/v1/enums/format-category.json", + "description": "High-level categories for creative formats (audio, video, display, native, dooh, rich_media, universal)" + }, "frequency-cap-scope": { "$ref": "/schemas/v1/enums/frequency-cap-scope.json", "description": "Scope for frequency cap application" diff --git a/schemas/cache/1.0.0/list-creative-formats-request.json b/schemas/cache/1.0.0/list-creative-formats-request.json index b40dcb7e..5950a599 100644 --- a/schemas/cache/1.0.0/list-creative-formats-request.json +++ b/schemas/cache/1.0.0/list-creative-formats-request.json @@ -7,16 +7,7 @@ "asset_types": { "description": "Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", "items": { - "enum": [ - "image", - "video", - "audio", - "text", - "html", - "javascript", - "url" - ], - "type": "string" + "$ref": "/schemas/v1/enums/asset-content-type.json" }, "type": "array" }, @@ -28,7 +19,7 @@ "format_ids": { "description": "Return only these specific format IDs (e.g., from get_products response)", "items": { - "$ref": "format-id.json" + "$ref": "/schemas/v1/core/format-id.json" }, "type": "array" }, @@ -57,14 +48,8 @@ "type": "string" }, "type": { - "description": "Filter by format type (technical categories with distinct requirements)", - "enum": [ - "audio", - "video", - "display", - "dooh" - ], - "type": "string" + "$ref": "/schemas/v1/enums/format-category.json", + "description": "Filter by format type (technical categories with distinct requirements)" } }, "title": "List Creative Formats Request", diff --git a/schemas/cache/1.0.0/update-media-buy-response.json b/schemas/cache/1.0.0/update-media-buy-response.json index 3c636993..7dacf2bc 100644 --- a/schemas/cache/1.0.0/update-media-buy-response.json +++ b/schemas/cache/1.0.0/update-media-buy-response.json @@ -13,24 +13,9 @@ }, "properties": { "affected_packages": { - "description": "Array of packages that were modified", + "description": "Array of packages that were modified with complete state information", "items": { - "additionalProperties": false, - "properties": { - "buyer_ref": { - "description": "Buyer's reference for the package", - "type": "string" - }, - "package_id": { - "description": "Publisher's package identifier", - "type": "string" - } - }, - "required": [ - "package_id", - "buyer_ref" - ], - "type": "object" + "$ref": "/schemas/v1/core/package.json" }, "type": "array" }, @@ -93,7 +78,7 @@ "errors": { "description": "Array of errors explaining why the operation failed", "items": { - "$ref": "error.json" + "$ref": "/schemas/v1/core/error.json" }, "minItems": 1, "type": "array" diff --git a/schemas/cache/1.0.0/webhook-payload.json b/schemas/cache/1.0.0/webhook-payload.json index ffd6ccd0..c0c4b7f7 100644 --- a/schemas/cache/1.0.0/webhook-payload.json +++ b/schemas/cache/1.0.0/webhook-payload.json @@ -14,7 +14,7 @@ "then": { "properties": { "result": { - "$ref": "create-media-buy-response.json" + "$ref": "/schemas/v1/media-buy/create-media-buy-response.json" } } } @@ -30,7 +30,7 @@ "then": { "properties": { "result": { - "$ref": "update-media-buy-response.json" + "$ref": "/schemas/v1/media-buy/update-media-buy-response.json" } } } @@ -46,7 +46,7 @@ "then": { "properties": { "result": { - "$ref": "sync-creatives-response.json" + "$ref": "/schemas/v1/media-buy/sync-creatives-response.json" } } } @@ -62,7 +62,7 @@ "then": { "properties": { "result": { - "$ref": "activate-signal-response.json" + "$ref": "/schemas/v1/signals/activate-signal-response.json" } } } @@ -78,7 +78,7 @@ "then": { "properties": { "result": { - "$ref": "get-signals-response.json" + "$ref": "/schemas/v1/signals/get-signals-response.json" } } } @@ -119,8 +119,20 @@ "media_buy_id": "mb_12345", "packages": [ { + "budget": 60000, "buyer_ref": "nike_ctv_package", - "package_id": "pkg_12345_001" + "creative_assignments": [], + "format_ids_to_provide": [ + { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "video_standard_30s" + } + ], + "pacing": "even", + "package_id": "pkg_12345_001", + "pricing_option_id": "cpm-fixed-sports", + "product_id": "ctv_sports_premium", + "status": "active" } ] }, @@ -214,7 +226,7 @@ ] }, "status": { - "$ref": "task-status.json", + "$ref": "/schemas/v1/enums/task-status.json", "description": "Current task status. Webhooks are only triggered for status changes after initial submission (e.g., submitted \u2192 input-required, submitted \u2192 completed, submitted \u2192 failed)." }, "task_id": { @@ -222,7 +234,7 @@ "type": "string" }, "task_type": { - "$ref": "task-type.json", + "$ref": "/schemas/v1/enums/task-type.json", "description": "Type of AdCP operation that triggered this webhook. Enables webhook handlers to route to appropriate processing logic." }, "timestamp": { diff --git a/src/adcp/__init__.py b/src/adcp/__init__.py index 547aefb2..9f19b9db 100644 --- a/src/adcp/__init__.py +++ b/src/adcp/__init__.py @@ -65,7 +65,6 @@ BothPreviewRender, BuildCreativeErrorResponse, BuildCreativeSuccessResponse, - CreatedPackageReference, CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, HtmlPreviewRender, @@ -228,8 +227,6 @@ "MediaBuy", "Package", "PackageRequest", - # Package type aliases - "CreatedPackageReference", # Status enums (for control flow) "CreativeStatus", "MediaBuyStatus", diff --git a/src/adcp/__main__.py b/src/adcp/__main__.py index 6035bb75..7cdc7684 100644 --- a/src/adcp/__main__.py +++ b/src/adcp/__main__.py @@ -83,11 +83,17 @@ async def execute_tool( # Tool dispatch mapping - single source of truth for ADCP methods # Types are filled at runtime to avoid circular imports +# Special case: list_tools takes no parameters (None means no request type) TOOL_DISPATCH: dict[str, tuple[str, type | None]] = { + "list_tools": ("list_tools", None), # Protocol introspection - no request type "get_products": ("get_products", None), "list_creative_formats": ("list_creative_formats", None), + "preview_creative": ("preview_creative", None), + "build_creative": ("build_creative", None), "sync_creatives": ("sync_creatives", None), "list_creatives": ("list_creatives", None), + "create_media_buy": ("create_media_buy", None), + "update_media_buy": ("update_media_buy", None), "get_media_buy_delivery": ("get_media_buy_delivery", None), "list_authorized_properties": ("list_authorized_properties", None), "get_signals": ("get_signals", None), @@ -122,8 +128,12 @@ async def _dispatch_tool(client: ADCPClient, tool_name: str, payload: dict[str, "list_creative_formats", gen.ListCreativeFormatsRequest, ) + TOOL_DISPATCH["preview_creative"] = ("preview_creative", gen.PreviewCreativeRequest) + TOOL_DISPATCH["build_creative"] = ("build_creative", gen.BuildCreativeRequest) TOOL_DISPATCH["sync_creatives"] = ("sync_creatives", gen.SyncCreativesRequest) TOOL_DISPATCH["list_creatives"] = ("list_creatives", gen.ListCreativesRequest) + TOOL_DISPATCH["create_media_buy"] = ("create_media_buy", gen.CreateMediaBuyRequest) + TOOL_DISPATCH["update_media_buy"] = ("update_media_buy", gen.UpdateMediaBuyRequest) TOOL_DISPATCH["get_media_buy_delivery"] = ( "get_media_buy_delivery", gen.GetMediaBuyDeliveryRequest, @@ -144,21 +154,38 @@ async def _dispatch_tool(client: ADCPClient, tool_name: str, payload: dict[str, available = ", ".join(sorted(TOOL_DISPATCH.keys())) return TaskResult( status=TaskStatus.FAILED, + success=False, error=f"Unknown tool: {tool_name}. Available tools: {available}", ) # Get method and request type method_name, request_type = TOOL_DISPATCH[tool_name] + method = getattr(client, method_name) - # Type guard - request_type should be initialized by this point + # Special case: list_tools takes no parameters and returns list[str], not TaskResult + if tool_name == "list_tools": + try: + tools = await method() + return TaskResult( + status=TaskStatus.COMPLETED, + data={"tools": tools}, + success=True, + ) + except Exception as e: + return TaskResult( + status=TaskStatus.FAILED, + success=False, + error=f"Failed to list tools: {e}", + ) + + # Type guard - request_type should be initialized by this point for non-list_tools if request_type is None: return TaskResult( status=TaskStatus.FAILED, + success=False, error=f"Internal error: {tool_name} request type not initialized", ) - method = getattr(client, method_name) - # Validate and invoke try: request = request_type(**payload) @@ -173,6 +200,7 @@ async def _dispatch_tool(client: ADCPClient, tool_name: str, payload: dict[str, return TaskResult( status=TaskStatus.FAILED, + success=False, error=f"Invalid request payload for {tool_name}:\n" + "\n".join(error_details), ) diff --git a/src/adcp/client.py b/src/adcp/client.py index b02cc44c..70bfc1f0 100644 --- a/src/adcp/client.py +++ b/src/adcp/client.py @@ -28,6 +28,10 @@ from adcp.types.stable import ( ActivateSignalRequest, ActivateSignalResponse, + BuildCreativeRequest, + BuildCreativeResponse, + CreateMediaBuyRequest, + CreateMediaBuyResponse, GetMediaBuyDeliveryRequest, GetMediaBuyDeliveryResponse, GetProductsRequest, @@ -46,6 +50,8 @@ ProvidePerformanceFeedbackResponse, SyncCreativesRequest, SyncCreativesResponse, + UpdateMediaBuyRequest, + UpdateMediaBuyResponse, WebhookPayload, ) from adcp.types.stable import ( @@ -574,6 +580,200 @@ async def provide_performance_feedback( return self.adapter._parse_response(raw_result, ProvidePerformanceFeedbackResponse) + async def create_media_buy( + self, + request: CreateMediaBuyRequest, + ) -> TaskResult[CreateMediaBuyResponse]: + """ + Create a new media buy reservation. + + Requests the agent to reserve inventory for a campaign. The agent returns a + media_buy_id that tracks this reservation and can be used for updates. + + Args: + request: Media buy creation parameters including: + - brand_manifest: Advertiser brand information and creative assets + - packages: List of package requests specifying desired inventory + - publisher_properties: Target properties for ad placement + - budget: Optional budget constraints + - start_date/end_date: Campaign flight dates + + Returns: + TaskResult containing CreateMediaBuyResponse with: + - media_buy_id: Unique identifier for this reservation + - status: Current state of the media buy + - packages: Confirmed package details + - Additional platform-specific metadata + + Example: + >>> from adcp import ADCPClient, CreateMediaBuyRequest + >>> client = ADCPClient(agent_config) + >>> request = CreateMediaBuyRequest( + ... brand_manifest=brand, + ... packages=[package_request], + ... publisher_properties=properties + ... ) + >>> result = await client.create_media_buy(request) + >>> if result.success: + ... media_buy_id = result.data.media_buy_id + """ + operation_id = create_operation_id() + params = request.model_dump(exclude_none=True) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_REQUEST, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="create_media_buy", + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + raw_result = await self.adapter.create_media_buy(params) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_RESPONSE, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="create_media_buy", + status=raw_result.status, + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + return self.adapter._parse_response(raw_result, CreateMediaBuyResponse) + + async def update_media_buy( + self, + request: UpdateMediaBuyRequest, + ) -> TaskResult[UpdateMediaBuyResponse]: + """ + Update an existing media buy reservation. + + Modifies a previously created media buy by updating packages or publisher + properties. The update operation uses discriminated unions to specify what + to change - either package details or targeting properties. + + Args: + request: Media buy update parameters including: + - media_buy_id: Identifier from create_media_buy response + - updates: Discriminated union specifying update type: + * UpdateMediaBuyPackagesRequest: Modify package selections + * UpdateMediaBuyPropertiesRequest: Change targeting properties + + Returns: + TaskResult containing UpdateMediaBuyResponse with: + - media_buy_id: The updated media buy identifier + - status: Updated state of the media buy + - packages: Updated package configurations + - Additional platform-specific metadata + + Example: + >>> from adcp import ADCPClient, UpdateMediaBuyPackagesRequest + >>> client = ADCPClient(agent_config) + >>> request = UpdateMediaBuyPackagesRequest( + ... media_buy_id="mb_123", + ... packages=[updated_package] + ... ) + >>> result = await client.update_media_buy(request) + >>> if result.success: + ... updated_packages = result.data.packages + """ + operation_id = create_operation_id() + params = request.model_dump(exclude_none=True) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_REQUEST, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="update_media_buy", + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + raw_result = await self.adapter.update_media_buy(params) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_RESPONSE, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="update_media_buy", + status=raw_result.status, + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + return self.adapter._parse_response(raw_result, UpdateMediaBuyResponse) + + async def build_creative( + self, + request: BuildCreativeRequest, + ) -> TaskResult[BuildCreativeResponse]: + """ + Generate production-ready creative assets. + + Requests the creative agent to build final deliverable assets in the target + format (e.g., VAST, DAAST, HTML5). This is typically called after previewing + and approving a creative manifest. + + Args: + request: Creative build parameters including: + - manifest: Creative manifest with brand info and content + - target_format_id: Desired output format identifier + - inputs: Optional user-provided inputs for template variables + - deployment: Platform or agent deployment configuration + + Returns: + TaskResult containing BuildCreativeResponse with: + - assets: Production-ready creative files (URLs or inline content) + - format_id: The generated format identifier + - manifest: The creative manifest used for generation + - metadata: Additional platform-specific details + + Example: + >>> from adcp import ADCPClient, BuildCreativeRequest + >>> client = ADCPClient(agent_config) + >>> request = BuildCreativeRequest( + ... manifest=creative_manifest, + ... target_format_id="vast_2.0", + ... inputs={"duration": 30} + ... ) + >>> result = await client.build_creative(request) + >>> if result.success: + ... vast_url = result.data.assets[0].url + """ + operation_id = create_operation_id() + params = request.model_dump(exclude_none=True) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_REQUEST, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="build_creative", + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + raw_result = await self.adapter.build_creative(params) + + self._emit_activity( + Activity( + type=ActivityType.PROTOCOL_RESPONSE, + operation_id=operation_id, + agent_id=self.agent_config.id, + task_type="build_creative", + status=raw_result.status, + timestamp=datetime.now(timezone.utc).isoformat(), + ) + ) + + return self.adapter._parse_response(raw_result, BuildCreativeResponse) + async def list_tools(self) -> list[str]: """ List available tools from the agent. diff --git a/src/adcp/protocols/a2a.py b/src/adcp/protocols/a2a.py index 9c882212..1cba7520 100644 --- a/src/adcp/protocols/a2a.py +++ b/src/adcp/protocols/a2a.py @@ -248,6 +248,18 @@ async def preview_creative(self, params: dict[str, Any]) -> TaskResult[Any]: """Generate preview URLs for a creative manifest.""" return await self._call_a2a_tool("preview_creative", params) + async def create_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Create media buy.""" + return await self._call_a2a_tool("create_media_buy", params) + + async def update_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Update media buy.""" + return await self._call_a2a_tool("update_media_buy", params) + + async def build_creative(self, params: dict[str, Any]) -> TaskResult[Any]: + """Build creative.""" + return await self._call_a2a_tool("build_creative", params) + async def list_tools(self) -> list[str]: """ List available tools from A2A agent. diff --git a/src/adcp/protocols/base.py b/src/adcp/protocols/base.py index 445f74d0..52958064 100644 --- a/src/adcp/protocols/base.py +++ b/src/adcp/protocols/base.py @@ -136,6 +136,21 @@ async def provide_performance_feedback(self, params: dict[str, Any]) -> TaskResu """Provide performance feedback.""" pass + @abstractmethod + async def create_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Create media buy.""" + pass + + @abstractmethod + async def update_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Update media buy.""" + pass + + @abstractmethod + async def build_creative(self, params: dict[str, Any]) -> TaskResult[Any]: + """Build creative.""" + pass + @abstractmethod async def list_tools(self) -> list[str]: """ diff --git a/src/adcp/protocols/mcp.py b/src/adcp/protocols/mcp.py index 7afb7ff8..d1265388 100644 --- a/src/adcp/protocols/mcp.py +++ b/src/adcp/protocols/mcp.py @@ -373,6 +373,18 @@ async def preview_creative(self, params: dict[str, Any]) -> TaskResult[Any]: """Generate preview URLs for a creative manifest.""" return await self._call_mcp_tool("preview_creative", params) + async def create_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Create media buy.""" + return await self._call_mcp_tool("create_media_buy", params) + + async def update_media_buy(self, params: dict[str, Any]) -> TaskResult[Any]: + """Update media buy.""" + return await self._call_mcp_tool("update_media_buy", params) + + async def build_creative(self, params: dict[str, Any]) -> TaskResult[Any]: + """Build creative.""" + return await self._call_mcp_tool("build_creative", params) + async def list_tools(self) -> list[str]: """List available tools from MCP agent.""" session = await self._get_session() diff --git a/src/adcp/types/__init__.py b/src/adcp/types/__init__.py index 6b725b4e..e3fa830b 100644 --- a/src/adcp/types/__init__.py +++ b/src/adcp/types/__init__.py @@ -29,8 +29,6 @@ # Build creative responses BuildCreativeErrorResponse, BuildCreativeSuccessResponse, - # Package aliases - CreatedPackageReference, # Create media buy responses CreateMediaBuyErrorResponse, CreateMediaBuySuccessResponse, @@ -104,7 +102,6 @@ Action, ActivateSignalRequest, ActivateSignalResponse, - AffectedPackage, AggregatedTotals, Asset, AssetSelectors, @@ -330,7 +327,6 @@ "ActivateSignalRequest", "ActivateSignalResponse", "ActivateSignalSuccessResponse", - "AffectedPackage", "AgentDeployment", "AgentDestination", "AggregatedTotals", @@ -376,7 +372,6 @@ "CreateMediaBuyRequest", "CreateMediaBuyResponse", "CreateMediaBuySuccessResponse", - "CreatedPackageReference", "Creative", "CreativeAgent", "CreativeAsset", diff --git a/src/adcp/types/_generated.py b/src/adcp/types/_generated.py index f2271134..e7c52900 100644 --- a/src/adcp/types/_generated.py +++ b/src/adcp/types/_generated.py @@ -10,7 +10,7 @@ DO NOT EDIT MANUALLY. Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas -Generation date: 2025-11-19 02:03:09 UTC +Generation date: 2025-11-20 17:15:08 UTC """ # ruff: noqa: E501, I001 from __future__ import annotations @@ -20,9 +20,10 @@ from adcp.types.generated_poc.activate_signal_response import ActivateSignalResponse, ActivateSignalResponse1, ActivateSignalResponse2 from adcp.types.generated_poc.activation_key import ActivationKey1, ActivationKey2 from adcp.types.generated_poc.adagents import AuthorizedAgents, AuthorizedAgents1, AuthorizedAgents2, AuthorizedAgents3, AuthorizedSalesAgents, Contact, PropertyId, PropertyTag, Tags +from adcp.types.generated_poc.asset_content_type import AssetContentType from adcp.types.generated_poc.asset_type import AssetTypeSchema, ContentLength, Dimensions, Duration, FileSize, Quality, Requirements, Type from adcp.types.generated_poc.audio_asset import AudioAsset -from adcp.types.generated_poc.brand_manifest import Asset, AssetType, BrandManifest, Colors, Disclaimer, FeedFormat, Fonts, Logo, Metadata, ProductCatalog, UpdateFrequency +from adcp.types.generated_poc.brand_manifest import Asset, BrandManifest, Colors, Disclaimer, FeedFormat, Fonts, Logo, Metadata, ProductCatalog, UpdateFrequency from adcp.types.generated_poc.build_creative_request import BuildCreativeRequest from adcp.types.generated_poc.build_creative_response import BuildCreativeResponse, BuildCreativeResponse1, BuildCreativeResponse2 from adcp.types.generated_poc.channels import AdvertisingChannels @@ -48,6 +49,7 @@ from adcp.types.generated_poc.error import Error from adcp.types.generated_poc.flat_rate_option import FlatRatePricingOption from adcp.types.generated_poc.format import AssetsRequired, AssetsRequired1, Format, FormatCard, FormatCardDetailed, Render, Responsive, Unit +from adcp.types.generated_poc.format_category import FormatCategory from adcp.types.generated_poc.format_id import FormatId from adcp.types.generated_poc.frequency_cap import FrequencyCap from adcp.types.generated_poc.frequency_cap_scope import FrequencyCapScope @@ -81,7 +83,7 @@ from adcp.types.generated_poc.preview_render import Embedding, PreviewRender, PreviewRender1, PreviewRender2, PreviewRender3 from adcp.types.generated_poc.pricing_model import PricingModel from adcp.types.generated_poc.product import DeliveryMeasurement, Product, ProductCard, ProductCardDetailed -from adcp.types.generated_poc.promoted_offerings import AssetSelectors, Offering, PromotedOfferings +from adcp.types.generated_poc.promoted_offerings import AssetSelectors, AssetType, Offering, PromotedOfferings from adcp.types.generated_poc.promoted_products import PromotedProducts from adcp.types.generated_poc.property import Identifier, Property, PropertyType, Tag from adcp.types.generated_poc.protocol_envelope import ProtocolEnvelope @@ -105,7 +107,7 @@ from adcp.types.generated_poc.tasks_list_response import DomainBreakdown, Task, TasksListResponse from adcp.types.generated_poc.text_asset import TextAsset from adcp.types.generated_poc.update_media_buy_request import Packages, Packages1, Packages2, Packages3, UpdateMediaBuyRequest, UpdateMediaBuyRequest1, UpdateMediaBuyRequest2 -from adcp.types.generated_poc.update_media_buy_response import AffectedPackage, UpdateMediaBuyResponse, UpdateMediaBuyResponse1, UpdateMediaBuyResponse2 +from adcp.types.generated_poc.update_media_buy_response import UpdateMediaBuyResponse, UpdateMediaBuyResponse1, UpdateMediaBuyResponse2 from adcp.types.generated_poc.url_asset import UrlAsset, UrlType from adcp.types.generated_poc.vast_asset import VastAsset1, VastAsset2, VastVersion from adcp.types.generated_poc.vcpm_auction_option import VcpmAuctionPricingOption @@ -115,7 +117,6 @@ from adcp.types.generated_poc.webhook_payload import WebhookPayload # Special imports for name collisions (qualified names for types defined in multiple modules) -from adcp.types.generated_poc.create_media_buy_response import Package as _PackageFromCreateMediaBuyResponse from adcp.types.generated_poc.package import Package as _PackageFromPackage # Backward compatibility aliases for renamed types @@ -125,7 +126,7 @@ __all__ = [ "Action", "ActivateSignalRequest", "ActivateSignalResponse", "ActivateSignalResponse1", "ActivateSignalResponse2", "ActivationKey1", "ActivationKey2", "AdvertisingChannels", - "AffectedPackage", "AggregatedTotals", "Asset", "AssetSelectors", "AssetType", + "AggregatedTotals", "Asset", "AssetContentType", "AssetSelectors", "AssetType", "AssetTypeSchema", "AssetsRequired", "AssetsRequired1", "AssignedPackage", "Assignments", "AudioAsset", "Authentication", "AuthorizedAgents", "AuthorizedAgents1", "AuthorizedAgents2", "AuthorizedAgents3", "AuthorizedSalesAgents", "AvailableMetric", "AvailableReportingFrequency", @@ -141,11 +142,11 @@ "Destination1", "Destination2", "Details", "Dimensions", "Direction", "Disclaimer", "Domain", "DomainBreakdown", "DoohMetrics", "Duration", "Embedding", "Error", "FeedFormat", "FeedbackSource", "Field1", "FieldModel", "FileSize", "Filters", "FlatRatePricingOption", - "Fonts", "Format", "FormatCard", "FormatCardDetailed", "FormatId", "FormatType", - "FrequencyCap", "FrequencyCapScope", "GeoCountryAnyOfItem", "GetMediaBuyDeliveryRequest", - "GetMediaBuyDeliveryResponse", "GetProductsRequest", "GetProductsResponse", - "GetSignalsRequest", "GetSignalsResponse", "HistoryItem", "HtmlAsset", "Identifier", - "ImageAsset", "Input", "Input2", "Input4", "JavascriptAsset", "LandingPage", + "Fonts", "Format", "FormatCard", "FormatCardDetailed", "FormatCategory", "FormatId", + "FormatType", "FrequencyCap", "FrequencyCapScope", "GeoCountryAnyOfItem", + "GetMediaBuyDeliveryRequest", "GetMediaBuyDeliveryResponse", "GetProductsRequest", + "GetProductsResponse", "GetSignalsRequest", "GetSignalsResponse", "HistoryItem", "HtmlAsset", + "Identifier", "ImageAsset", "Input", "Input2", "Input4", "JavascriptAsset", "LandingPage", "ListAuthorizedPropertiesRequest", "ListAuthorizedPropertiesResponse", "ListCreativeFormatsRequest", "ListCreativeFormatsResponse", "ListCreativesRequest", "ListCreativesResponse", "Logo", "MarkdownAsset", "MarkdownFlavor", "Measurement", @@ -176,6 +177,5 @@ "UpdateMediaBuyResponse", "UpdateMediaBuyResponse1", "UpdateMediaBuyResponse2", "UrlAsset", "UrlType", "ValidationMode", "VastAsset1", "VastAsset2", "VastVersion", "VcpmAuctionPricingOption", "VcpmFixedRatePricingOption", "VenueBreakdownItem", "VideoAsset", - "ViewThreshold", "ViewThreshold1", "WebhookAsset", "WebhookPayload", - "_PackageFromCreateMediaBuyResponse", "_PackageFromPackage" + "ViewThreshold", "ViewThreshold1", "WebhookAsset", "WebhookPayload", "_PackageFromPackage" ] diff --git a/src/adcp/types/aliases.py b/src/adcp/types/aliases.py index c81da494..5c150e7f 100644 --- a/src/adcp/types/aliases.py +++ b/src/adcp/types/aliases.py @@ -101,10 +101,6 @@ ) # Import all generated types that need semantic aliases -from adcp.types._generated import ( - # Package types (from name collision resolution) - _PackageFromCreateMediaBuyResponse as CreatedPackageInternal, -) from adcp.types._generated import ( _PackageFromPackage as FullPackageInternal, ) @@ -265,17 +261,8 @@ creative_assignments, format_ids_to_provide, impressions, pacing, targeting_overlay """ -CreatedPackageReference = CreatedPackageInternal -"""Minimal package reference with only IDs returned after creation. - -This is NOT the full Package type - it's a lightweight reference returned -in CreateMediaBuySuccessResponse to indicate which packages were created. - -Used in: -- CreateMediaBuySuccessResponse.packages (list of created package references) - -Fields: buyer_ref, package_id only -""" +# Note: CreatedPackageReference was removed as the schema now uses the canonical +# Package type everywhere. Use `Package` directly instead. # ============================================================================ # PUBLISHER PROPERTIES ALIASES - Selection Type Discriminated Unions @@ -757,7 +744,6 @@ def filter_products(props: PublisherProperties) -> None: "UpdateMediaBuySuccessResponse", "UpdateMediaBuyErrorResponse", # Package type aliases - "CreatedPackageReference", "Package", # Publisher properties types "PropertyId", diff --git a/src/adcp/types/generated_poc/activate_signal_request.py b/src/adcp/types/generated_poc/activate_signal_request.py index 2e6c4ca5..8e4b491a 100644 --- a/src/adcp/types/generated_poc/activate_signal_request.py +++ b/src/adcp/types/generated_poc/activate_signal_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: activate-signal-request.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -22,10 +22,10 @@ class ActivateSignalRequest(AdCPBaseModel): description='Initiator-provided context included in the request payload. Agents must echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.' ), ] = None - destinations: Annotated[ + deployments: Annotated[ list[destination.Destination1 | destination.Destination2], Field( - description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', + description='Target deployment(s) for activation. If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.', min_length=1, ), ] diff --git a/src/adcp/types/generated_poc/activate_signal_response.py b/src/adcp/types/generated_poc/activate_signal_response.py index 95bafcb2..17d099df 100644 --- a/src/adcp/types/generated_poc/activate_signal_response.py +++ b/src/adcp/types/generated_poc/activate_signal_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: activate-signal-response.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -24,7 +24,7 @@ class ActivateSignalResponse1(AdCPBaseModel): ] = None deployments: Annotated[ list[deployment.Deployment1 | deployment.Deployment2], - Field(description='Array of deployment results for each destination'), + Field(description='Array of deployment results for each deployment target'), ] diff --git a/src/adcp/types/generated_poc/asset_content_type.py b/src/adcp/types/generated_poc/asset_content_type.py new file mode 100644 index 00000000..9ce44d04 --- /dev/null +++ b/src/adcp/types/generated_poc/asset_content_type.py @@ -0,0 +1,23 @@ +# generated by datamodel-codegen: +# filename: asset-content-type.json +# timestamp: 2025-11-20T17:15:07+00:00 + +from __future__ import annotations + +from enum import Enum + + +class AssetContentType(Enum): + image = 'image' + video = 'video' + audio = 'audio' + text = 'text' + markdown = 'markdown' + html = 'html' + css = 'css' + javascript = 'javascript' + vast = 'vast' + daast = 'daast' + promoted_offerings = 'promoted_offerings' + url = 'url' + webhook = 'webhook' diff --git a/src/adcp/types/generated_poc/brand_manifest.py b/src/adcp/types/generated_poc/brand_manifest.py index d26b0765..5dbb17d9 100644 --- a/src/adcp/types/generated_poc/brand_manifest.py +++ b/src/adcp/types/generated_poc/brand_manifest.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: brand-manifest.json -# timestamp: 2025-11-18T04:34:42+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -10,12 +10,7 @@ from adcp.types.base import AdCPBaseModel from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - -class AssetType(Enum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' +from . import asset_content_type class Asset(AdCPBaseModel): @@ -23,7 +18,12 @@ class Asset(AdCPBaseModel): extra='forbid', ) asset_id: Annotated[str, Field(description='Unique identifier for this asset')] - asset_type: Annotated[AssetType, Field(description='Type of asset')] + asset_type: Annotated[ + asset_content_type.AssetContentType, + Field( + description='Type of asset. Note: Brand manifests typically contain basic media assets (image, video, audio, text). Code assets (html, javascript, css) and ad markup (vast, daast) are usually not part of brand asset libraries.' + ), + ] description: Annotated[str | None, Field(description='Asset description or usage notes')] = None duration_seconds: Annotated[ float | None, Field(description='Video/audio duration in seconds') diff --git a/src/adcp/types/generated_poc/create_media_buy_response.py b/src/adcp/types/generated_poc/create_media_buy_response.py index 04b78d7f..ee4bcef6 100644 --- a/src/adcp/types/generated_poc/create_media_buy_response.py +++ b/src/adcp/types/generated_poc/create_media_buy_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: create-media-buy-response.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -9,15 +9,23 @@ from adcp.types.base import AdCPBaseModel from pydantic import AwareDatetime, ConfigDict, Field, RootModel -from . import error +from . import error, package -class Package(AdCPBaseModel): +class CreateMediaBuyResponse2(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for the package")] - package_id: Annotated[str, Field(description="Publisher's unique identifier for the package")] + context: Annotated[ + dict[str, Any] | None, + Field( + description='Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.' + ), + ] = None + errors: Annotated[ + list[error.Error], + Field(description='Array of errors explaining why the operation failed', min_length=1), + ] class CreateMediaBuyResponse1(AdCPBaseModel): @@ -37,22 +45,9 @@ class CreateMediaBuyResponse1(AdCPBaseModel): media_buy_id: Annotated[ str, Field(description="Publisher's unique identifier for the created media buy") ] - packages: Annotated[list[Package], Field(description='Array of created packages')] - - -class CreateMediaBuyResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - context: Annotated[ - dict[str, Any] | None, - Field( - description='Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.' - ), - ] = None - errors: Annotated[ - list[error.Error], - Field(description='Array of errors explaining why the operation failed', min_length=1), + packages: Annotated[ + list[package.Package], + Field(description='Array of created packages with complete state information'), ] diff --git a/src/adcp/types/generated_poc/deployment.py b/src/adcp/types/generated_poc/deployment.py index 89abf372..0042226d 100644 --- a/src/adcp/types/generated_poc/deployment.py +++ b/src/adcp/types/generated_poc/deployment.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: deployment.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -20,7 +20,7 @@ class Deployment1(AdCPBaseModel): activation_key: Annotated[ activation_key_1.ActivationKey1 | activation_key_1.ActivationKey2 | None, Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this destination.', + description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', title='Activation Key', ), ] = None @@ -36,7 +36,7 @@ class Deployment1(AdCPBaseModel): ), ] = None is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this destination') + bool, Field(description='Whether signal is currently active on this deployment') ] platform: Annotated[str, Field(description='Platform identifier for DSPs')] type: Annotated[ @@ -53,11 +53,11 @@ class Deployment2(AdCPBaseModel): activation_key: Annotated[ activation_key_1.ActivationKey1 | activation_key_1.ActivationKey2 | None, Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this destination.', + description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', title='Activation Key', ), ] = None - agent_url: Annotated[AnyUrl, Field(description='URL identifying the destination agent')] + agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] deployed_at: Annotated[ AwareDatetime | None, Field(description='Timestamp when activation completed (if is_live=true)'), @@ -70,7 +70,7 @@ class Deployment2(AdCPBaseModel): ), ] = None is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this destination') + bool, Field(description='Whether signal is currently active on this deployment') ] type: Annotated[ Literal['agent'], diff --git a/src/adcp/types/generated_poc/destination.py b/src/adcp/types/generated_poc/destination.py index 8cd7ea2f..d4817608 100644 --- a/src/adcp/types/generated_poc/destination.py +++ b/src/adcp/types/generated_poc/destination.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: destination.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -23,7 +23,7 @@ class Destination1(AdCPBaseModel): ] type: Annotated[ Literal['platform'], - Field(description='Discriminator indicating this is a platform-based destination'), + Field(description='Discriminator indicating this is a platform-based deployment'), ] @@ -35,9 +35,9 @@ class Destination2(AdCPBaseModel): str | None, Field(description='Optional account identifier on the agent') ] = None agent_url: Annotated[ - AnyUrl, Field(description='URL identifying the destination agent (for sales agents, etc.)') + AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') ] type: Annotated[ Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based destination'), + Field(description='Discriminator indicating this is an agent URL-based deployment'), ] diff --git a/src/adcp/types/generated_poc/format.py b/src/adcp/types/generated_poc/format.py index 0e2b0f87..804ca2cb 100644 --- a/src/adcp/types/generated_poc/format.py +++ b/src/adcp/types/generated_poc/format.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: format.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -10,25 +10,10 @@ from adcp.types.base import AdCPBaseModel from pydantic import AnyUrl, ConfigDict, Field +from . import asset_content_type, format_category from . import format_id as format_id_1 -class AssetType(Enum): - 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 AssetsRequired(AdCPBaseModel): asset_id: Annotated[ str, @@ -42,7 +27,7 @@ class AssetsRequired(AdCPBaseModel): description="Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." ), ] = None - asset_type: Annotated[AssetType, Field(description='Type of asset')] + asset_type: Annotated[asset_content_type.AssetContentType, Field(description='Type of asset')] item_type: Annotated[ Literal['individual'], Field(description='Discriminator indicating this is an individual asset requirement'), @@ -64,7 +49,7 @@ class Asset(AdCPBaseModel): description="Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only." ), ] = None - asset_type: Annotated[AssetType, Field(description='Type of asset')] + asset_type: Annotated[asset_content_type.AssetContentType, Field(description='Type of asset')] required: Annotated[ bool | None, Field(description='Whether this asset is required in each repetition') ] = None @@ -174,16 +159,6 @@ class Render(AdCPBaseModel): ] -class Type(Enum): - audio = 'audio' - video = 'video' - display = 'display' - native = 'native' - dooh = 'dooh' - rich_media = 'rich_media' - universal = 'universal' - - class Format(AdCPBaseModel): model_config = ConfigDict( extra='forbid', @@ -253,7 +228,7 @@ class Format(AdCPBaseModel): ), ] = None type: Annotated[ - Type, + format_category.FormatCategory, Field( description='Media type of this format - determines rendering method and asset requirements' ), diff --git a/src/adcp/types/generated_poc/format_category.py b/src/adcp/types/generated_poc/format_category.py new file mode 100644 index 00000000..2e7f2ba5 --- /dev/null +++ b/src/adcp/types/generated_poc/format_category.py @@ -0,0 +1,17 @@ +# generated by datamodel-codegen: +# filename: format-category.json +# timestamp: 2025-11-20T17:15:07+00:00 + +from __future__ import annotations + +from enum import Enum + + +class FormatCategory(Enum): + audio = 'audio' + video = 'video' + display = 'display' + native = 'native' + dooh = 'dooh' + rich_media = 'rich_media' + universal = 'universal' diff --git a/src/adcp/types/generated_poc/get_signals_request.py b/src/adcp/types/generated_poc/get_signals_request.py index a1921f71..141519ad 100644 --- a/src/adcp/types/generated_poc/get_signals_request.py +++ b/src/adcp/types/generated_poc/get_signals_request.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: get-signals-request.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -24,10 +24,10 @@ class DeliverTo(AdCPBaseModel): countries: Annotated[ list[Country], Field(description='Countries where signals will be used (ISO codes)') ] - destinations: Annotated[ + deployments: Annotated[ list[destination.Destination1 | destination.Destination2], Field( - description='List of destination platforms (DSPs, sales agents, etc.). If the authenticated caller matches one of these destinations, activation keys will be included in the response.', + description='List of deployment targets (DSPs, sales agents, etc.). If the authenticated caller matches one of these deployment targets, activation keys will be included in the response.', min_length=1, ), ] @@ -66,7 +66,7 @@ class GetSignalsRequest(AdCPBaseModel): ), ] = None deliver_to: Annotated[ - DeliverTo, Field(description='Destination platforms where signals need to be activated') + DeliverTo, Field(description='Deployment targets where signals need to be activated') ] filters: Annotated[Filters | None, Field(description='Filters to refine results')] = None max_results: Annotated[ diff --git a/src/adcp/types/generated_poc/get_signals_response.py b/src/adcp/types/generated_poc/get_signals_response.py index 3ac9e5fa..c6b1698c 100644 --- a/src/adcp/types/generated_poc/get_signals_response.py +++ b/src/adcp/types/generated_poc/get_signals_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: get-signals-response.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -37,7 +37,7 @@ class Signal(AdCPBaseModel): data_provider: Annotated[str, Field(description='Name of the data provider')] deployments: Annotated[ list[deployment.Deployment1 | deployment.Deployment2], - Field(description='Array of destination deployments'), + Field(description='Array of deployment targets'), ] description: Annotated[str, Field(description='Detailed signal description')] name: Annotated[str, Field(description='Human-readable signal name')] diff --git a/src/adcp/types/generated_poc/list_creative_formats_request.py b/src/adcp/types/generated_poc/list_creative_formats_request.py index 3666beda..ec364b94 100644 --- a/src/adcp/types/generated_poc/list_creative_formats_request.py +++ b/src/adcp/types/generated_poc/list_creative_formats_request.py @@ -1,33 +1,15 @@ # generated by datamodel-codegen: # filename: list-creative-formats-request.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations -from enum import Enum from typing import Annotated, Any from adcp.types.base import AdCPBaseModel from pydantic import ConfigDict, Field -from . import format_id - - -class AssetType(Enum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - html = 'html' - javascript = 'javascript' - url = 'url' - - -class Type(Enum): - audio = 'audio' - video = 'video' - display = 'display' - dooh = 'dooh' +from . import asset_content_type, format_category, format_id class ListCreativeFormatsRequest(AdCPBaseModel): @@ -35,7 +17,7 @@ class ListCreativeFormatsRequest(AdCPBaseModel): extra='forbid', ) asset_types: Annotated[ - list[AssetType] | None, + list[asset_content_type.AssetContentType] | None, Field( description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags." ), @@ -86,7 +68,7 @@ class ListCreativeFormatsRequest(AdCPBaseModel): str | None, Field(description='Search for formats by name (case-insensitive partial match)') ] = None type: Annotated[ - Type | None, + format_category.FormatCategory | None, Field( description='Filter by format type (technical categories with distinct requirements)' ), diff --git a/src/adcp/types/generated_poc/update_media_buy_response.py b/src/adcp/types/generated_poc/update_media_buy_response.py index 68ee139c..80b40209 100644 --- a/src/adcp/types/generated_poc/update_media_buy_response.py +++ b/src/adcp/types/generated_poc/update_media_buy_response.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: # filename: update-media-buy-response.json -# timestamp: 2025-11-18T03:35:10+00:00 +# timestamp: 2025-11-20T17:15:07+00:00 from __future__ import annotations @@ -9,15 +9,23 @@ from adcp.types.base import AdCPBaseModel from pydantic import AwareDatetime, ConfigDict, Field, RootModel -from . import error +from . import error, package -class AffectedPackage(AdCPBaseModel): +class UpdateMediaBuyResponse2(AdCPBaseModel): model_config = ConfigDict( extra='forbid', ) - buyer_ref: Annotated[str, Field(description="Buyer's reference for the package")] - package_id: Annotated[str, Field(description="Publisher's package identifier")] + context: Annotated[ + dict[str, Any] | None, + Field( + description='Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.' + ), + ] = None + errors: Annotated[ + list[error.Error], + Field(description='Array of errors explaining why the operation failed', min_length=1), + ] class UpdateMediaBuyResponse1(AdCPBaseModel): @@ -25,7 +33,8 @@ class UpdateMediaBuyResponse1(AdCPBaseModel): extra='forbid', ) affected_packages: Annotated[ - list[AffectedPackage] | None, Field(description='Array of packages that were modified') + list[package.Package] | None, + Field(description='Array of packages that were modified with complete state information'), ] = None buyer_ref: Annotated[str, Field(description="Buyer's reference identifier for the media buy")] context: Annotated[ @@ -41,22 +50,6 @@ class UpdateMediaBuyResponse1(AdCPBaseModel): media_buy_id: Annotated[str, Field(description="Publisher's identifier for the media buy")] -class UpdateMediaBuyResponse2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - context: Annotated[ - dict[str, Any] | None, - Field( - description='Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.' - ), - ] = None - errors: Annotated[ - list[error.Error], - Field(description='Array of errors explaining why the operation failed', min_length=1), - ] - - class UpdateMediaBuyResponse(RootModel[UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2]): root: Annotated[ UpdateMediaBuyResponse1 | UpdateMediaBuyResponse2, diff --git a/src/adcp/types/stable.py b/src/adcp/types/stable.py index d067c2f3..8dad698d 100644 --- a/src/adcp/types/stable.py +++ b/src/adcp/types/stable.py @@ -26,7 +26,6 @@ # Core request/response types ActivateSignalRequest, ActivateSignalResponse, - AffectedPackage, AggregatedTotals, # Assets Asset, @@ -247,7 +246,6 @@ "ActivateSignalRequest", "ActivateSignalResponse", "Action", - "AffectedPackage", "AggregatedTotals", "BuildCreativeRequest", "BuildCreativeResponse", diff --git a/tests/test_cli.py b/tests/test_cli.py index fd007b49..d3f3703f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -279,6 +279,59 @@ def test_invalid_protocol(self, tmp_path, monkeypatch): class TestCLIIntegration: """Integration tests for CLI (with mocked network calls).""" + @pytest.mark.asyncio + async def test_list_tools_dispatch(self): + """Test that list_tools is in TOOL_DISPATCH and handled correctly.""" + from unittest.mock import AsyncMock, MagicMock + + from adcp.__main__ import _dispatch_tool + from adcp.client import ADCPClient + from adcp.types.core import AgentConfig, Protocol + + # Create mock client + config = AgentConfig( + id="test", + agent_uri="https://test.example.com", + protocol=Protocol.MCP, + ) + + client = ADCPClient(config) + + # Mock the list_tools method to return a list of tools + client.list_tools = AsyncMock(return_value=["get_products", "list_creative_formats"]) + + # Test that list_tools can be dispatched + result = await _dispatch_tool(client, "list_tools", {}) + + assert result.success is True + assert result.data == {"tools": ["get_products", "list_creative_formats"]} + + @pytest.mark.asyncio + async def test_invalid_tool_name(self): + """Test that invalid tool names return proper error.""" + from unittest.mock import AsyncMock + + from adcp.__main__ import _dispatch_tool + from adcp.client import ADCPClient + from adcp.types.core import AgentConfig, Protocol, TaskStatus + + # Create mock client + config = AgentConfig( + id="test", + agent_uri="https://test.example.com", + protocol=Protocol.MCP, + ) + + client = ADCPClient(config) + + # Test invalid tool name + result = await _dispatch_tool(client, "invalid_tool_name", {}) + + assert result.success is False + assert result.status == TaskStatus.FAILED + assert "Unknown tool" in result.error + assert "list_tools" in result.error # Should be in available tools list + class TestSpecialCharactersInPayload: """Test that CLI handles special characters in payloads.""" diff --git a/tests/test_client.py b/tests/test_client.py index 2a084780..14a562c9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -141,6 +141,10 @@ async def test_all_client_methods(): assert hasattr(client, "get_signals") assert hasattr(client, "activate_signal") assert hasattr(client, "provide_performance_feedback") + assert hasattr(client, "preview_creative") + assert hasattr(client, "create_media_buy") + assert hasattr(client, "update_media_buy") + assert hasattr(client, "build_creative") @pytest.mark.parametrize( @@ -159,7 +163,7 @@ async def test_all_client_methods(): "signal_spec": "test", "deliver_to": { "countries": ["US"], - "destinations": [{"type": "platform", "platform": "test"}], + "deployments": [{"type": "platform", "platform": "test"}], }, }, ), @@ -168,7 +172,7 @@ async def test_all_client_methods(): "ActivateSignalRequest", { "signal_agent_segment_id": "test", - "destinations": [{"type": "platform", "platform": "test"}], + "deployments": [{"type": "platform", "platform": "test"}], }, ), ( @@ -183,6 +187,8 @@ async def test_all_client_methods(): "performance_index": 0.5, }, ), + # Note: preview_creative, create_media_buy, update_media_buy, and build_creative + # are tested separately with full request validation since their schemas are complex ], ) @pytest.mark.asyncio diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 23e8cfd9..fd81cb5e 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -285,41 +285,28 @@ def test_semantic_aliases_can_be_imported_from_main_package(): def test_package_type_aliases_imports(): - """Test that Package type aliases can be imported.""" - from adcp import CreatedPackageReference, Package - from adcp.types import CreatedPackageReference as TypesCreatedPackageReference + """Test that Package type alias can be imported.""" + from adcp import Package from adcp.types import Package as TypesPackage - from adcp.types.aliases import CreatedPackageReference as AliasCreatedPackageReference from adcp.types.aliases import Package as AliasPackage # Verify all import paths work assert Package is TypesPackage assert Package is AliasPackage - assert CreatedPackageReference is TypesCreatedPackageReference - assert CreatedPackageReference is AliasCreatedPackageReference def test_package_type_aliases_point_to_correct_modules(): - """Test that Package aliases point to the correct generated types.""" - from adcp import CreatedPackageReference, Package - from adcp.types._generated import ( - _PackageFromCreateMediaBuyResponse, - _PackageFromPackage, - ) + """Test that Package alias points to the correct generated type.""" + from adcp import Package + from adcp.types._generated import _PackageFromPackage # Package should point to the full domain package assert Package is _PackageFromPackage - # CreatedPackageReference should point to the response package - assert CreatedPackageReference is _PackageFromCreateMediaBuyResponse - - # Verify they're different types - assert Package is not CreatedPackageReference - def test_package_type_aliases_have_correct_fields(): - """Test that Package type aliases have the expected fields.""" - from adcp import CreatedPackageReference, Package + """Test that Package type alias has the expected fields.""" + from adcp import Package # Package should have all operational fields package_fields = set(Package.__annotations__.keys()) @@ -342,43 +329,26 @@ def test_package_type_aliases_have_correct_fields(): f"Expected: {expected_package_fields}, Got: {package_fields}" ) - # CreatedPackageReference should only have IDs - created_fields = set(CreatedPackageReference.__annotations__.keys()) - expected_created_fields = {"buyer_ref", "package_id"} - assert created_fields == expected_created_fields, ( - f"CreatedPackageReference fields mismatch. " - f"Expected: {expected_created_fields}, Got: {created_fields}" - ) - def test_package_type_aliases_in_exports(): - """Test that Package type aliases are properly exported.""" + """Test that Package type alias is properly exported.""" import adcp import adcp.types.aliases as aliases_module # Check main package exports assert hasattr(adcp, "Package") - assert hasattr(adcp, "CreatedPackageReference") assert "Package" in adcp.__all__ - assert "CreatedPackageReference" in adcp.__all__ # Check aliases module exports assert hasattr(aliases_module, "Package") - assert hasattr(aliases_module, "CreatedPackageReference") assert "Package" in aliases_module.__all__ - assert "CreatedPackageReference" in aliases_module.__all__ def test_package_aliases_can_instantiate(): - """Test that Package type aliases can be used to create instances.""" - from adcp import CreatedPackageReference, Package + """Test that Package type alias can be used to create instances.""" + from adcp import Package from adcp.types import PackageStatus - # Create a CreatedPackageReference (minimal fields) - created = CreatedPackageReference(buyer_ref="buyer-123", package_id="pkg-456") - assert created.buyer_ref == "buyer-123" - assert created.package_id == "pkg-456" - # Create a Package (all required fields) pkg = Package(package_id="pkg-789", status=PackageStatus.draft) assert pkg.package_id == "pkg-789"