diff --git a/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json b/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json index cda3d0274..16f24c4ad 100644 --- a/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json @@ -75,6 +75,11 @@ ] } ] + }, + "context": { + "type": "object", + "description": "Initiator-provided context included in the request payload. Agentsmust echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json b/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json index 0c7cc8e3a..ac43af3f9 100644 --- a/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json @@ -43,6 +43,11 @@ ], "additionalProperties": false } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ @@ -68,6 +73,11 @@ "$ref": "/schemas/v1/core/error.json" }, "minItems": 1 + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json b/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json index 500771c77..7a1c98f73 100644 --- a/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json @@ -57,6 +57,11 @@ "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "description": "End date for reporting period (YYYY-MM-DD)" + }, + "context": { + "type": "object", + "description": "Initiator-provided context included in the request payload. Agentsmust echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", + "additionalProperties": true } }, "additionalProperties": false diff --git a/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json b/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json index df74e8211..b6b391431 100644 --- a/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json @@ -180,11 +180,28 @@ "type": "number", "description": "Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)", "minimum": 0 + }, + "pricing_model": { + "$ref": "/schemas/v1/enums/pricing-model.json", + "description": "The pricing model used for this package (e.g., cpm, cpcv, cpp). Indicates how the package is billed and which metrics are most relevant for optimization." + }, + "rate": { + "type": "number", + "description": "The pricing rate for this package in the specified currency. For fixed-rate pricing, this is the agreed rate (e.g., CPM rate of 12.50 means $12.50 per 1,000 impressions). For auction-based pricing, this represents the effective rate based on actual delivery.", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code (e.g., USD, EUR, GBP) for this package's pricing. Indicates the currency in which the rate and spend values are denominated. Different packages can use different currencies when supported by the publisher.", + "pattern": "^[A-Z]{3}$" } }, "required": [ "package_id", - "spend" + "spend", + "pricing_model", + "rate", + "currency" ] } ] @@ -236,6 +253,11 @@ "items": { "$ref": "/schemas/v1/core/error.json" } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json b/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json index af0391e1f..13d91793d 100644 --- a/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json @@ -54,6 +54,11 @@ } }, "additionalProperties": false + }, + "context": { + "type": "object", + "description": "Initiator-provided context included in the request payload. Agentsmust echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", + "additionalProperties": true } }, "required": [], diff --git a/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json b/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json index 950ec4c00..472a7e748 100644 --- a/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json @@ -18,6 +18,11 @@ "items": { "$ref": "/schemas/v1/core/error.json" } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json b/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json index c6a7a86b6..5ec824b56 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json @@ -14,6 +14,11 @@ "description": "Publisher domain to filter by (e.g., 'cnn.com', 'espn.com')" }, "minItems": 1 + }, + "context": { + "type": "object", + "description": "Initiator-provided context included in the request payload. Agentsmust echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", + "additionalProperties": true } }, "additionalProperties": false diff --git a/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json b/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json index 03524f422..ce9bc5fb7 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json @@ -55,6 +55,11 @@ "items": { "$ref": "/schemas/v1/core/error.json" } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json b/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json index cbfef081a..0a2491ff0 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json @@ -61,6 +61,11 @@ "name_search": { "type": "string", "description": "Search for formats by name (case-insensitive partial match)" + }, + "context": { + "type": "object", + "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.", + "additionalProperties": true } }, "additionalProperties": false diff --git a/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json b/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json index e6a1a49a0..01a9b6792 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json @@ -52,6 +52,11 @@ "items": { "$ref": "/schemas/v1/core/error.json" } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json b/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json index ca1fda9e2..821378390 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json @@ -181,6 +181,11 @@ "sub_assets" ] } + }, + "context": { + "type": "object", + "description": "Initiator-provided context included in the request payload. Agentsmust echo this value back unchanged in responses and webhooks. Use for UI/session hints, correlation tokens, or tracking metadata.", + "additionalProperties": true } }, "additionalProperties": false, diff --git a/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json b/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json index f192b59c3..ab1eea6ff 100644 --- a/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json @@ -344,6 +344,11 @@ } }, "additionalProperties": false + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json b/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json index b8d4800d3..dca9369c6 100644 --- a/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json @@ -54,6 +54,11 @@ "push_notification_config": { "$ref": "/schemas/v1/core/push-notification-config.json", "description": "Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL)." + }, + "context": { + "type": "object", + "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.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json b/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json index fd423d832..33af7e693 100644 --- a/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json @@ -94,6 +94,11 @@ ], "additionalProperties": false } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ @@ -117,6 +122,11 @@ "$ref": "/schemas/v1/core/error.json" }, "minItems": 1 + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json b/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json index b86b31b45..c70be97d1 100644 --- a/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json +++ b/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json @@ -85,6 +85,11 @@ "push_notification_config": { "$ref": "/schemas/v1/core/push-notification-config.json", "description": "Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time." + }, + "context": { + "type": "object", + "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.", + "additionalProperties": true } }, "oneOf": [ diff --git a/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json b/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json index 5d8618484..28fff487e 100644 --- a/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json +++ b/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json @@ -46,6 +46,11 @@ ], "additionalProperties": false } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ @@ -70,6 +75,11 @@ "$ref": "/schemas/v1/core/error.json" }, "minItems": 1 + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_signals_get-signals-request_json.json b/schemas/v1/_schemas_v1_signals_get-signals-request_json.json index 5af233199..e4b4b5776 100644 --- a/schemas/v1/_schemas_v1_signals_get-signals-request_json.json +++ b/schemas/v1/_schemas_v1_signals_get-signals-request_json.json @@ -77,6 +77,11 @@ "type": "integer", "description": "Maximum number of results to return", "minimum": 1 + }, + "context": { + "type": "object", + "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.", + "additionalProperties": true } }, "required": [ diff --git a/schemas/v1/_schemas_v1_signals_get-signals-response_json.json b/schemas/v1/_schemas_v1_signals_get-signals-response_json.json index 06c00e7a5..5c5b447b3 100644 --- a/schemas/v1/_schemas_v1_signals_get-signals-response_json.json +++ b/schemas/v1/_schemas_v1_signals_get-signals-response_json.json @@ -90,6 +90,11 @@ "items": { "$ref": "/schemas/v1/core/error.json" } + }, + "context": { + "type": "object", + "description": "Initiator-provided context echoed inside the task payload. Opaque metadata such as UI/session hints, correlation tokens, or tracking identifiers.", + "additionalProperties": true } }, "required": [ diff --git a/src/admin/blueprints/api.py b/src/admin/blueprints/api.py index 7c698815a..1a53d318c 100644 --- a/src/admin/blueprints/api.py +++ b/src/admin/blueprints/api.py @@ -17,90 +17,9 @@ api_bp = Blueprint("api", __name__) -@api_bp.route("/formats/list", methods=["GET"]) -def list_formats(): - """List all available creative formats from registered creative agents. - - Query params: - tenant_id: Optional tenant ID for tenant-specific formats - - Returns: - JSON with format list grouped by agent URL - """ - from src.core.format_resolver import list_available_formats - - tenant_id = request.args.get("tenant_id") - - logger.info(f"[/api/formats/list] Fetching formats for tenant_id={tenant_id}") - - try: - # Get all formats from creative agent registry - formats = list_available_formats(tenant_id=tenant_id) - - logger.info(f"[/api/formats/list] Successfully fetched {len(formats)} formats from creative agents") - - if not formats: - logger.warning(f"[/api/formats/list] No formats returned for tenant_id={tenant_id}") - return jsonify( - { - "agents": {}, - "total_formats": 0, - "warning": "No creative formats available. Check creative agent configuration.", - } - ) - - # Group formats by agent URL for frontend compatibility - agents = {} - for fmt in formats: - agent_url = fmt.agent_url - if agent_url not in agents: - agents[agent_url] = [] - - # Convert to dict for JSON serialization - try: - # Handle FormatId object - extract string value - format_id_str = fmt.format_id.id if hasattr(fmt.format_id, "id") else str(fmt.format_id) - - format_dict = { - "format_id": format_id_str, - "name": fmt.name, - "type": fmt.type, - "category": fmt.category, - "description": fmt.description, - "iab_specification": fmt.iab_specification, - } - - # Add dimensions if available - dimensions = fmt.get_primary_dimensions() - if dimensions: - width, height = dimensions - format_dict["dimensions"] = f"{width}x{height}" - - agents[agent_url].append(format_dict) - except Exception as fmt_error: - logger.error( - f"[/api/formats/list] Error serializing format {getattr(fmt, 'format_id', 'unknown')}: {fmt_error}", - exc_info=True, - ) - # Continue with other formats - - logger.info( - f"[/api/formats/list] Returning {len(agents)} agent(s) with {sum(len(fmts) for fmts in agents.values())} total formats" - ) - return jsonify({"agents": agents, "total_formats": len(formats)}) - except Exception as e: - logger.error(f"[/api/formats/list] Error listing formats for tenant_id={tenant_id}: {e}", exc_info=True) - return ( - jsonify( - { - "error": str(e), - "agents": {}, - "error_type": type(e).__name__, - "message": "Failed to fetch creative formats. Check server logs for details.", - } - ), - 500, - ) +# Note: /formats/list route moved to format_search.py blueprint +# (registered at /api/formats/list via format_search_bp) +# This avoids route conflicts and uses the proper async registry pattern @api_bp.route("/health", methods=["GET"]) diff --git a/src/core/schema_adapters.py b/src/core/schema_adapters.py index 5e3f5f6a9..c00a17d53 100644 --- a/src/core/schema_adapters.py +++ b/src/core/schema_adapters.py @@ -162,6 +162,7 @@ class GetProductsResponse(AdCPBaseModel): # Fields from generated schema (flexible - accepts dicts or objects) products: list[Any] = Field(..., description="List of matching products") errors: list[Any] | None = Field(None, description="Task-specific errors") + context: dict[str, Any] | None = None def __str__(self) -> str: """Return human-readable message for protocol layer. @@ -374,6 +375,7 @@ class ListCreativeFormatsResponse(AdCPBaseModel): formats: list[Any] = Field(..., description="Full format definitions per AdCP spec") creative_agents: list[Any] | None = Field(None, description="Creative agents providing additional formats") errors: list[Any] | None = Field(None, description="Task-specific errors and warnings") + context: dict[str, Any] | None = None def __str__(self) -> str: """Return human-readable message for protocol layer. @@ -444,6 +446,7 @@ class ListAuthorizedPropertiesResponse(AdCPBaseModel): ) last_updated: str | None = Field(None, description="ISO 8601 timestamp of when authorization list was last updated") errors: list[Any] | None = Field(None, description="Task-specific errors and warnings") + context: dict[str, Any] | None = None def dict(self, **kwargs): """Override dict to use model_dump with exclude_none=True for AdCP compliance. @@ -686,6 +689,7 @@ class GetMediaBuyDeliveryResponse(AdCPBaseModel): sequence_number: int | None = None next_expected_at: str | None = None errors: list[Any] | None = None + context: dict[str, Any] | None = None def __str__(self) -> str: """Return human-readable message for protocol layer.""" @@ -713,6 +717,7 @@ class GetSignalsResponse(AdCPBaseModel): signals: list[Any] = Field(..., description="Array of matching signals") errors: list[Any] | None = None + context: dict[str, Any] | None = None def __str__(self) -> str: """Return human-readable summary of signals.""" @@ -775,6 +780,7 @@ class ListCreativesResponse(AdCPBaseModel): creatives: list[Any] = Field(..., description="Array of creative assets") format_summary: dict[str, int] | None = None status_summary: dict[str, int] | None = None + context: dict[str, Any] | None = None def __str__(self) -> str: """Generate human-readable message from query_summary.""" diff --git a/src/core/schemas.py b/src/core/schemas.py index 32ec72979..bf901bd80 100644 --- a/src/core/schemas.py +++ b/src/core/schemas.py @@ -1196,6 +1196,7 @@ class GetProductsRequest(AdCPBaseModel): None, description="Structured filters for product discovery", ) + context: dict[str, Any] | None = None class Error(BaseModel): @@ -1608,6 +1609,7 @@ class SyncCreativesRequest(AdCPBaseModel): None, description="Application-level webhook config (NOTE: Protocol-level push notifications via A2A/MCP transport take precedence)", ) + context: dict[str, Any] | None = None class SyncSummary(BaseModel): @@ -1753,6 +1755,8 @@ def validate_timezone_aware(self): raise ValueError("created_before must be timezone-aware (ISO 8601 with timezone)") return self + context: dict[str, Any] | None = None + class QuerySummary(BaseModel): """Summary of the query that was executed.""" @@ -2489,6 +2493,7 @@ class GetMediaBuyDeliveryRequest(AdCPBaseModel): push_notification_config: PushNotificationConfig | None = Field( None, description="Push notification configuration for async task updates." ) + context: dict[str, Any] | None = None # AdCP-compliant delivery models @@ -2503,6 +2508,7 @@ class DeliveryTotals(BaseModel): completion_rate: float | None = Field( None, ge=0, le=1, description="Video completion rate (completions/impressions)" ) + context: dict[str, Any] | None = None class PackageDelivery(BaseModel): @@ -2747,6 +2753,7 @@ class UpdateMediaBuyRequest(AdCPBaseModel): None, description="Application-level webhook config (NOTE: Protocol-level push notifications via A2A/MCP transport take precedence)", ) + context: dict[str, Any] | None = None today: date | None = Field(None, exclude=True, description="For testing/simulation only - not part of AdCP spec") # NOTE: No Python validator needed for oneOf constraint - AdCP schema enforces media_buy_id/buyer_ref oneOf @@ -2812,6 +2819,7 @@ class AdapterPackageDelivery(BaseModel): package_id: str impressions: int spend: float + context: dict[str, Any] | None = None class AdapterGetMediaBuyDeliveryResponse(AdCPBaseModel): @@ -3148,6 +3156,8 @@ def limit(self) -> int | None: warnings.warn("limit is deprecated. Use max_results instead.", DeprecationWarning, stacklevel=2) return self.max_results + context: dict[str, Any] | None = None + class GetSignalsResponse(AdCPBaseModel): """Response containing available signals (AdCP v2.4 spec compliant). @@ -3341,6 +3351,8 @@ def normalize_tags(cls, data): data["tags"] = [tag.lower().replace("-", "_") for tag in data["tags"]] return data + context: dict[str, Any] | None = None + class ListAuthorizedPropertiesResponse(AdCPBaseModel): """Response payload for list_authorized_properties task (AdCP v2.4 spec compliant). diff --git a/templates/components/inventory_profile_editor.html b/templates/components/inventory_profile_editor.html index 23a2ae6f2..1af80b675 100644 --- a/templates/components/inventory_profile_editor.html +++ b/templates/components/inventory_profile_editor.html @@ -191,6 +191,9 @@