Motivation
GetProductsRequest.fields is a 29-value enum (product_id, name, description, publisher_properties, channels, format_ids, …, trusted_match) that lets buyers ask for a subset of Product fields for lightweight discovery — e.g. "give me just IDs and pricing for comparison". The wire schema requires product_id and name always be returned regardless of selection.
This is mechanical post-processing on the response. The seller's adapter already builds a full Product; honoring fields is model_dump(include=…)-shaped work that every adopter will write the same way (or, more likely, forget to implement, leaving the buyer's lightweight-discovery contract silently broken).
Parent tracker: #491.
Current state
Salesagent: does not honor fields at all — _get_products_impl (src/core/tools/products.py:145-470+) returns full Product objects unconditionally.
SDK: MediaBuyHandler.get_products (src/adcp/decisioning/handler.py:1015-1033) is a pass-through, no projection. The wire enum lives at src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1194-1225 (Field1).
Proposed API
Framework intercepts in MediaBuyHandler.get_products after the adapter returns, before envelope projection. No capability declaration needed — projection is a pure no-op when fields is absent.
# src/adcp/decisioning/handler.py — get_products
async def get_products(self, params, context=None):
...
response = await _invoke_platform_method(...)
if params.fields:
response = _project_product_fields(response, params.fields)
return response
# src/adcp/decisioning/_get_products_helpers.py (new)
_REQUIRED_FIELDS: frozenset[str] = frozenset({\"product_id\", \"name\"})
def _project_product_fields(
response: GetProductsResponse,
fields: list[Field1],
) -> GetProductsResponse:
\"\"\"Drop product fields not in `fields`. `product_id` and `name`
are always retained per the wire-schema description.\"\"\"
keep = {f.value for f in fields} | _REQUIRED_FIELDS
projected = [
Product.model_validate(p.model_dump(include=keep, exclude_none=True))
for p in response.products
]
return response.model_copy(update={\"products\": projected})
Acceptance criteria
Out of scope
- Projecting fields on
proposals[] — proposals carry product references, not full products (no wire-defined projection there)
- Per-field cost/latency hints — wire spec doesn't define those
- Capability declaration — projection is unconditional (no
fields_projection_supported flag in spec)
Cross-references
Motivation
GetProductsRequest.fieldsis a 29-value enum (product_id,name,description,publisher_properties,channels,format_ids, …,trusted_match) that lets buyers ask for a subset ofProductfields for lightweight discovery — e.g. "give me just IDs and pricing for comparison". The wire schema requiresproduct_idandnamealways be returned regardless of selection.This is mechanical post-processing on the response. The seller's adapter already builds a full
Product; honoringfieldsismodel_dump(include=…)-shaped work that every adopter will write the same way (or, more likely, forget to implement, leaving the buyer's lightweight-discovery contract silently broken).Parent tracker: #491.
Current state
Salesagent: does not honor
fieldsat all —_get_products_impl(src/core/tools/products.py:145-470+) returns fullProductobjects unconditionally.SDK:
MediaBuyHandler.get_products(src/adcp/decisioning/handler.py:1015-1033) is a pass-through, no projection. The wire enum lives atsrc/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1194-1225(Field1).Proposed API
Framework intercepts in
MediaBuyHandler.get_productsafter the adapter returns, before envelope projection. No capability declaration needed — projection is a pure no-op whenfieldsis absent.Acceptance criteria
_project_product_fieldsdrops every field not infields ∪ {product_id, name}fieldsisNoneor empty, response is unchanged (no allocation)product_idandnameare always present, even when not infieldsextra='allow'ext fields on Product are preserved (don't strip them)fields=[product_id]returns Products with onlyproduct_id+nameOut of scope
proposals[]— proposals carry product references, not full products (no wire-defined projection there)fields_projection_supportedflag in spec)Cross-references
src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1194-1225(Field1)fields:annotation)src/adcp/decisioning/webhook_emit.py(framework intercepts at the seam)