Skip to content

fix(client): preserve canonical format options in get_products #1005

Description

@bokelley

Bug

ADCPClient.get_products() in v7.0.0-rc drops valid canonical format_options and can return an empty product list with FORMAT_PROJECTION_FAILED diagnostics.

Reproduction

Return a valid canonical product from an adapter:

TaskResult(
    status=TaskStatus.COMPLETED,
    success=True,
    data={
        "products": [{
            "product_id": "p1",
            "name": "Product 1",
            "description": "A test product",
            "publisher_properties": [
                {"selection_type": "all", "publisher_domain": "pub.example.com"}
            ],
            "format_options": [{
                "format_option_id": "p1-display",
                "format_kind": "image",
                "params": {"width": 300, "height": 250},
            }],
            "delivery_type": "non_guaranteed",
            "pricing_options": [{
                "pricing_model": "cpm",
                "pricing_option_id": "po1",
                "currency": "USD",
            }],
            "reporting_capabilities": {
                "available_reporting_frequencies": ["daily"],
                "expected_delay_minutes": 0,
                "timezone": "UTC",
                "supports_webhooks": False,
                "available_metrics": ["impressions"],
                "date_range_support": "date_range",
            },
        }]
    },
)

Calling the primary get_products() method produces no usable canonical product. Direct GetProductsResponse.model_validate() of the response succeeds.

Root cause

_canonicalize_get_products_result() first parses every response as LegacyGetProductsResponse. The generated legacy ProductFormatDeclaration does not declare canonical format_kind or params, so Pydantic discards them before project_legacy_product() runs. The projector consequently receives entries such as:

{"format_option_id": "p1-display", "canonical_formats_only": false, "experimental": false}

instead of the canonical declaration sent on the wire.

Expected behavior

The primary canonical client should parse a valid canonical GetProductsResponse without passing it through a lossy legacy model. Legacy-only responses should continue through the existing projection path.

Impact

Canonical-only sellers can return a valid wire response while Python v7.0.0-rc buyers silently lose those products. Raw MCP consumers are unaffected; the regression is in the high-level Python client canonicalization path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions