From c6f65d6ae20053ce1e968f1e34ae593545b2ba89 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 21 May 2026 11:23:16 -0400 Subject: [PATCH 1/2] feat(preview-utils): adopt 3.1.0-beta.2 self-rendering product_card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AdCP 3.1.0-beta.2 changed product_card from creative-agent-rendered ({format_id, manifest}) to a self-contained visual card ({image, title, description, price_label, cta_label}). The card IS the preview now; no creative-agent round-trip is required. batchPreviewProducts rewritten to extract product_card.image?.url directly. The creativeAgentClient + options parameters are retained for signature compatibility (renamed to _-prefixed unused args) and marked @deprecated; planned removal in 8.0 final or 9.0. format_card / batchPreviewFormats are unchanged — only product_card had this spec migration in 3.1.0-beta.2. Adopter migration: existing calls keep working (PreviewResult[] still populated with previewUrl from the new inline image.url). Direct product.product_card?.image?.url access is the new recommended path. Part of the #1902 8.0-beta sweep (5/5 structural breaks closed — CI should now be green on the foundation stack). --- .changeset/product-card-self-rendering.md | 18 ++ src/lib/server/wire-spec-fields.generated.ts | 120 ++++++------- src/lib/utils/preview-utils.ts | 174 ++++--------------- 3 files changed, 109 insertions(+), 203 deletions(-) create mode 100644 .changeset/product-card-self-rendering.md diff --git a/.changeset/product-card-self-rendering.md b/.changeset/product-card-self-rendering.md new file mode 100644 index 000000000..cebf9c616 --- /dev/null +++ b/.changeset/product-card-self-rendering.md @@ -0,0 +1,18 @@ +--- +'@adcp/sdk': major +--- + +feat(preview-utils): adopt 3.1.0-beta.2 self-rendering `product_card` shape + +AdCP 3.1.0-beta.2 changed `product_card` from a creative-agent-rendered shape (`{ format_id, manifest }`) to a self-contained visual card (`{ image, title, description, price_label, cta_label }`). The card IS the preview — no creative-agent round-trip required. (Schema note: "Receivers render the card directly from these fields.") + +**Changes:** +- `batchPreviewProducts` rewritten: extracts `product_card.image?.url` directly from the inline card instead of round-tripping through `creativeAgent.previewCreative()`. +- `creativeAgentClient` and `options` parameters retained for signature compatibility (renamed to `_creativeAgentClient` / `_options` with the unused-args eslint pragma). Will be removed in 8.0 final or 9.0. +- `format_card` and `batchPreviewFormats` are **unchanged** — only `product_card` had this spec migration in 3.1.0-beta.2. + +**Adopter migration:** +- Calls to `batchPreviewProducts(products, creativeAgent)` keep returning `PreviewResult[]` with `previewUrl` populated from the new inline `image.url`. No code change required. +- Direct field access (`product.product_card?.image?.url`) is now the recommended path; `batchPreviewProducts` is `@deprecated`. + +Part of the #1902 8.0-beta sweep (5/5 structural breaks closed — **CI should now be green** on the foundation stack). diff --git a/src/lib/server/wire-spec-fields.generated.ts b/src/lib/server/wire-spec-fields.generated.ts index 446274fa4..c0b7a28d1 100644 --- a/src/lib/server/wire-spec-fields.generated.ts +++ b/src/lib/server/wire-spec-fields.generated.ts @@ -1,6 +1,6 @@ // AUTO-GENERATED by scripts/generate-wire-spec-fields.ts. DO NOT EDIT. -// Source: schemas/cache/3.0.12/**/*-request.json -// Generated at: 2026-05-17T08:43:49.535Z +// Source: schemas/cache/3.1.0-beta.2/**/*-request.json +// Generated at: 2026-05-21T15:22:36.051Z import type { AcquireRightsRequest, @@ -53,149 +53,149 @@ import type { * pollution or shared-state mutation of the allowlist. */ export const WIRE_SPEC_FIELDS = Object.freeze({ - /** schemas/cache/3.0.12/brand/acquire-rights-request.json */ + /** schemas/cache/3.1.0-beta.2/brand/acquire-rights-request.json */ AcquireRightsRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","buyer","campaign","context","ext","idempotency_key","pricing_option_id","push_notification_config","revocation_webhook","rights_id"]) as readonly string[], + fields: Object.freeze(["account","buyer","campaign","context","ext","idempotency_key","pricing_option_id","push_notification_config","revocation_webhook","rights_id"]) as readonly string[], __type: null as unknown as AcquireRightsRequest, }), - /** schemas/cache/3.0.12/signals/activate-signal-request.json */ + /** schemas/cache/3.1.0-beta.2/signals/activate-signal-request.json */ ActivateSignalRequest: Object.freeze({ - fields: Object.freeze(["account","action","adcp_major_version","context","destinations","ext","idempotency_key","pricing_option_id","signal_agent_segment_id"]) as readonly string[], + fields: Object.freeze(["account","action","context","destinations","ext","idempotency_key","pricing_option_id","signal_agent_segment_id"]) as readonly string[], __type: null as unknown as ActivateSignalRequest, }), - /** schemas/cache/3.0.12/media-buy/build-creative-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/build-creative-request.json */ BuildCreativeRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","brand","concept_id","context","creative_id","creative_manifest","ext","idempotency_key","include_preview","item_limit","macro_values","media_buy_id","message","package_id","preview_inputs","preview_output_format","preview_quality","quality","target_format_id","target_format_ids"]) as readonly string[], + fields: Object.freeze(["account","brand","concept_id","context","creative_id","creative_manifest","ext","idempotency_key","include_preview","item_limit","macro_values","media_buy_id","message","package_id","preview_inputs","preview_output_format","preview_quality","quality","target_format_id","target_format_ids"]) as readonly string[], __type: null as unknown as BuildCreativeRequest, }), - /** schemas/cache/3.0.12/content-standards/calibrate-content-request.json */ + /** schemas/cache/3.1.0-beta.2/content-standards/calibrate-content-request.json */ CalibrateContentRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","artifact","context","ext","idempotency_key","standards_id"]) as readonly string[], + fields: Object.freeze(["artifact","context","ext","idempotency_key","standards_id"]) as readonly string[], __type: null as unknown as CalibrateContentRequest, }), - /** schemas/cache/3.0.12/collection/create-collection-list-request.json */ + /** schemas/cache/3.1.0-beta.2/collection/create-collection-list-request.json */ CreateCollectionListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","base_collections","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], + fields: Object.freeze(["account","base_collections","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], __type: null as unknown as CreateCollectionListRequest, }), - /** schemas/cache/3.0.12/content-standards/create-content-standards-request.json */ + /** schemas/cache/3.1.0-beta.2/content-standards/create-content-standards-request.json */ CreateContentStandardsRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope"]) as readonly string[], + fields: Object.freeze(["calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope"]) as readonly string[], __type: null as unknown as CreateContentStandardsRequest, }), - /** schemas/cache/3.0.12/media-buy/create-media-buy-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/create-media-buy-request.json */ CreateMediaBuyRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","advertiser_industry","agency_estimate_number","artifact_webhook","brand","context","end_time","ext","idempotency_key","invoice_recipient","io_acceptance","packages","plan_id","po_number","proposal_id","push_notification_config","reporting_webhook","start_time","total_budget"]) as readonly string[], + fields: Object.freeze(["account","advertiser_industry","agency_estimate_number","artifact_webhook","brand","context","end_time","ext","idempotency_key","invoice_recipient","io_acceptance","packages","plan_id","po_number","proposal_id","push_notification_config","reporting_webhook","start_time","total_budget"]) as readonly string[], __type: null as unknown as CreateMediaBuyRequest, }), - /** schemas/cache/3.0.12/property/create-property-list-request.json */ + /** schemas/cache/3.1.0-beta.2/property/create-property-list-request.json */ CreatePropertyListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","base_properties","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], + fields: Object.freeze(["account","base_properties","brand","context","description","ext","filters","idempotency_key","name"]) as readonly string[], __type: null as unknown as CreatePropertyListRequest, }), - /** schemas/cache/3.0.12/collection/delete-collection-list-request.json */ + /** schemas/cache/3.1.0-beta.2/collection/delete-collection-list-request.json */ DeleteCollectionListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","context","ext","idempotency_key","list_id"]) as readonly string[], + fields: Object.freeze(["account","context","ext","idempotency_key","list_id"]) as readonly string[], __type: null as unknown as DeleteCollectionListRequest, }), - /** schemas/cache/3.0.12/property/delete-property-list-request.json */ + /** schemas/cache/3.1.0-beta.2/property/delete-property-list-request.json */ DeletePropertyListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","context","ext","idempotency_key","list_id"]) as readonly string[], + fields: Object.freeze(["account","context","ext","idempotency_key","list_id"]) as readonly string[], __type: null as unknown as DeletePropertyListRequest, }), - /** schemas/cache/3.0.12/media-buy/get-media-buy-delivery-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/get-media-buy-delivery-request.json */ GetMediaBuyDeliveryRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","attribution_window","context","end_date","ext","include_package_daily_breakdown","media_buy_ids","reporting_dimensions","start_date","status_filter"]) as readonly string[], + fields: Object.freeze(["account","attribution_window","context","end_date","ext","include_package_daily_breakdown","include_window_breakdown","media_buy_ids","reporting_dimensions","start_date","status_filter","time_granularity"]) as readonly string[], __type: null as unknown as GetMediaBuyDeliveryRequest, }), - /** schemas/cache/3.0.12/media-buy/log-event-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/log-event-request.json */ LogEventRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","event_source_id","events","ext","idempotency_key","test_event_code"]) as readonly string[], + fields: Object.freeze(["context","event_source_id","events","ext","idempotency_key","test_event_code"]) as readonly string[], __type: null as unknown as LogEventRequest, }), - /** schemas/cache/3.0.12/media-buy/provide-performance-feedback-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/provide-performance-feedback-request.json */ ProvidePerformanceFeedbackRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","creative_id","ext","feedback_source","idempotency_key","measurement_period","media_buy_id","metric_type","package_id","performance_index"]) as readonly string[], + fields: Object.freeze(["context","creative_id","ext","feedback_source","idempotency_key","measurement_period","media_buy_id","metric_type","package_id","performance_index"]) as readonly string[], __type: null as unknown as ProvidePerformanceFeedbackRequest, }), - /** schemas/cache/3.0.12/governance/report-plan-outcome-request.json */ + /** schemas/cache/3.1.0-beta.2/governance/report-plan-outcome-request.json */ ReportPlanOutcomeRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","check_id","context","delivery","error","ext","governance_context","idempotency_key","outcome","plan_id","purchase_type","seller_response"]) as readonly string[], + fields: Object.freeze(["check_id","context","delivery","error","ext","governance_context","idempotency_key","outcome","plan_id","purchase_type","seller_response"]) as readonly string[], __type: null as unknown as ReportPlanOutcomeRequest, }), - /** schemas/cache/3.0.12/account/report-usage-request.json */ + /** schemas/cache/3.1.0-beta.2/account/report-usage-request.json */ ReportUsageRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","ext","idempotency_key","reporting_period","usage"]) as readonly string[], + fields: Object.freeze(["context","ext","idempotency_key","reporting_period","usage"]) as readonly string[], __type: null as unknown as ReportUsageRequest, }), - /** schemas/cache/3.0.12/sponsored-intelligence/si-initiate-session-request.json */ + /** schemas/cache/3.1.0-beta.2/sponsored-intelligence/si-initiate-session-request.json */ SIInitiateSessionRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","ext","idempotency_key","identity","intent","media_buy_id","offering_id","offering_token","placement","supported_capabilities"]) as readonly string[], + fields: Object.freeze(["context","ext","idempotency_key","identity","intent","media_buy_id","offering_id","offering_token","placement","supported_capabilities"]) as readonly string[], __type: null as unknown as SIInitiateSessionRequest, }), - /** schemas/cache/3.0.12/sponsored-intelligence/si-send-message-request.json */ + /** schemas/cache/3.1.0-beta.2/sponsored-intelligence/si-send-message-request.json */ SISendMessageRequest: Object.freeze({ - fields: Object.freeze(["action_response","adcp_major_version","context","ext","idempotency_key","message","session_id"]) as readonly string[], + fields: Object.freeze(["action_response","context","ext","idempotency_key","message","session_id"]) as readonly string[], __type: null as unknown as SISendMessageRequest, }), - /** schemas/cache/3.0.12/account/sync-accounts-request.json */ + /** schemas/cache/3.1.0-beta.2/account/sync-accounts-request.json */ SyncAccountsRequest: Object.freeze({ - fields: Object.freeze(["accounts","adcp_major_version","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config"]) as readonly string[], + fields: Object.freeze(["accounts","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config"]) as readonly string[], __type: null as unknown as SyncAccountsRequest, }), - /** schemas/cache/3.0.12/media-buy/sync-audiences-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/sync-audiences-request.json */ SyncAudiencesRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","audiences","context","delete_missing","ext","idempotency_key"]) as readonly string[], + fields: Object.freeze(["account","audiences","context","delete_missing","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncAudiencesRequest, }), - /** schemas/cache/3.0.12/media-buy/sync-catalogs-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/sync-catalogs-request.json */ SyncCatalogsRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","catalog_ids","catalogs","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], + fields: Object.freeze(["account","catalog_ids","catalogs","context","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], __type: null as unknown as SyncCatalogsRequest, }), - /** schemas/cache/3.0.12/creative/sync-creatives-request.json */ + /** schemas/cache/3.1.0-beta.2/creative/sync-creatives-request.json */ SyncCreativesRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","assignments","context","creative_ids","creatives","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], + fields: Object.freeze(["account","assignments","context","creative_ids","creatives","delete_missing","dry_run","ext","idempotency_key","push_notification_config","validation_mode"]) as readonly string[], __type: null as unknown as SyncCreativesRequest, }), - /** schemas/cache/3.0.12/media-buy/sync-event-sources-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/sync-event-sources-request.json */ SyncEventSourcesRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","context","delete_missing","event_sources","ext","idempotency_key"]) as readonly string[], + fields: Object.freeze(["account","context","delete_missing","event_sources","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncEventSourcesRequest, }), - /** schemas/cache/3.0.12/account/sync-governance-request.json */ + /** schemas/cache/3.1.0-beta.2/account/sync-governance-request.json */ SyncGovernanceRequest: Object.freeze({ - fields: Object.freeze(["accounts","adcp_major_version","context","ext","idempotency_key"]) as readonly string[], + fields: Object.freeze(["accounts","context","ext","idempotency_key"]) as readonly string[], __type: null as unknown as SyncGovernanceRequest, }), - /** schemas/cache/3.0.12/governance/sync-plans-request.json */ + /** schemas/cache/3.1.0-beta.2/governance/sync-plans-request.json */ SyncPlansRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","ext","idempotency_key","plans"]) as readonly string[], + fields: Object.freeze(["context","ext","idempotency_key","plans"]) as readonly string[], __type: null as unknown as SyncPlansRequest, }), - /** schemas/cache/3.0.12/collection/update-collection-list-request.json */ + /** schemas/cache/3.1.0-beta.2/collection/update-collection-list-request.json */ UpdateCollectionListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","base_collections","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], + fields: Object.freeze(["account","base_collections","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], __type: null as unknown as UpdateCollectionListRequest, }), - /** schemas/cache/3.0.12/content-standards/update-content-standards-request.json */ + /** schemas/cache/3.1.0-beta.2/content-standards/update-content-standards-request.json */ UpdateContentStandardsRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope","standards_id"]) as readonly string[], + fields: Object.freeze(["calibration_exemplars","context","ext","idempotency_key","policies","registry_policy_ids","scope","standards_id"]) as readonly string[], __type: null as unknown as UpdateContentStandardsRequest, }), - /** schemas/cache/3.0.12/media-buy/update-media-buy-request.json */ + /** schemas/cache/3.1.0-beta.2/media-buy/update-media-buy-request.json */ UpdateMediaBuyRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","canceled","cancellation_reason","context","end_time","ext","idempotency_key","invoice_recipient","media_buy_id","new_packages","packages","paused","push_notification_config","reporting_webhook","revision","start_time"]) as readonly string[], + fields: Object.freeze(["account","canceled","cancellation_reason","context","end_time","ext","idempotency_key","invoice_recipient","media_buy_id","new_packages","packages","paused","push_notification_config","reporting_webhook","revision","start_time"]) as readonly string[], __type: null as unknown as UpdateMediaBuyRequest, }), - /** schemas/cache/3.0.12/property/update-property-list-request.json */ + /** schemas/cache/3.1.0-beta.2/property/update-property-list-request.json */ UpdatePropertyListRequest: Object.freeze({ - fields: Object.freeze(["account","adcp_major_version","base_properties","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], + fields: Object.freeze(["account","base_properties","brand","context","description","ext","filters","idempotency_key","list_id","name","webhook_url"]) as readonly string[], __type: null as unknown as UpdatePropertyListRequest, }), - /** schemas/cache/3.0.12/brand/update-rights-request.json */ + /** schemas/cache/3.1.0-beta.2/brand/update-rights-request.json */ UpdateRightsRequest: Object.freeze({ - fields: Object.freeze(["adcp_major_version","context","end_date","ext","idempotency_key","impression_cap","paused","pricing_option_id","push_notification_config","rights_id"]) as readonly string[], + fields: Object.freeze(["account","context","end_date","ext","idempotency_key","impression_cap","paused","pricing_option_id","push_notification_config","rights_id"]) as readonly string[], __type: null as unknown as UpdateRightsRequest, }), } as const); diff --git a/src/lib/utils/preview-utils.ts b/src/lib/utils/preview-utils.ts index f07f601ca..7179b9b59 100644 --- a/src/lib/utils/preview-utils.ts +++ b/src/lib/utils/preview-utils.ts @@ -91,25 +91,31 @@ export function clearPreviewCache(): void { } /** - * Generate batch previews for products with product_card manifests + * Extract preview URLs from products' `product_card` fields. * - * Products with product_card fields will have their cards rendered via the creative agent. - * Products without product_card will be returned with no preview. + * AdCP 3.1.0-beta.2 changed `product_card` from a creative-agent-rendered + * shape (`{ format_id, manifest }`) to a self-contained visual card + * (`{ image, title, description, price_label, cta_label }`). The card IS + * the preview — no creative-agent round-trip required. This function now + * extracts the image URL directly from the inline card. * - * @param products - Array of products to preview - * @param creativeAgentClient - ADCP client configured for creative agent - * @param options - Preview generation options - * @returns Array of preview results matching input products + * Products with no `product_card`, or with a card that lacks an `image.url`, + * return a result with no `previewUrl`. The function preserves its + * `Promise` return shape so existing adopters' code paths + * keep compiling and behaving correctly. + * + * @param products - Array of products to extract previews from + * @param creativeAgentClient - Retained for signature compatibility; unused + * under 3.1.0-beta.2's self-rendering card model. Will be removed in + * 8.0 final (or 9.0 at the latest) — pass any value through during the + * beta cycle. + * @param options - Retained for signature compatibility. Cache fields + * (`cacheTtl`, `skipCache`) are unused — there's no expensive call to + * cache. Will be removed alongside `creativeAgentClient`. + * @returns Array of preview results matching input products by index. * * @example * ```typescript - * const creativeAgent = new SingleAgentClient({ - * id: 'creative', - * name: 'Creative Agent', - * agent_uri: 'https://creative.adcontextprotocol.org/mcp', - * protocol: 'mcp' - * }); - * * const previews = await batchPreviewProducts(products, creativeAgent); * previews.forEach(p => { * if (p.previewUrl) { @@ -117,141 +123,23 @@ export function clearPreviewCache(): void { * } * }); * ``` + * + * @deprecated Use `product.product_card?.image?.url` directly. This wrapper + * only exists for 8.0-beta migration ergonomics and will be removed. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars export async function batchPreviewProducts( products: Product[], - creativeAgentClient: SingleAgentClient, - options: BatchPreviewOptions = {} + _creativeAgentClient: SingleAgentClient, + _options: BatchPreviewOptions = {} ): Promise { - const cacheTtl = options.cacheTtl ?? 3600000; // 1 hour default - const skipCache = options.skipCache ?? false; - - // Collect all products that have product_card manifests - const previewRequests: { - product: Product; - formatId: FormatID; - manifest: any; - cacheKey: string; - inputName: string; - }[] = []; - - const results: PreviewResult[] = []; - - products.forEach(product => { - if (product.product_card) { - const cacheKey = getCacheKey(product.product_card.format_id, product.product_card.manifest); - - // Check cache first - if (!skipCache) { - const cached = getCachedPreview(cacheKey, cacheTtl); - if (cached) { - results.push({ - item: product, - previewUrl: cached.previewUrl, - previewId: cached.previewId, - }); - return; - } - } - - previewRequests.push({ - product, - formatId: product.product_card.format_id, - manifest: product.product_card.manifest, - cacheKey, - inputName: product.name || 'Product Card', - }); - } else { - // No product_card, return product with no preview - results.push({ - item: product, - }); - } - }); - - // If all were cached or none have product_card, return early - if (previewRequests.length === 0) { - return results; - } - - // Batch preview using preview_creative with inputs array - // Group by format_id since preview_creative takes one format_id - const groupedByFormat = new Map(); - - previewRequests.forEach(req => { - const formatKey = `${req.formatId.agent_url}:${req.formatId.id}`; - if (!groupedByFormat.has(formatKey)) { - groupedByFormat.set(formatKey, []); + return products.map(product => { + const imageUrl = product.product_card?.image?.url; + if (imageUrl) { + return { item: product, previewUrl: imageUrl }; } - groupedByFormat.get(formatKey)!.push(req); + return { item: product }; }); - - // Process each product individually - // Note: Each product has different manifest data, so we can't truly batch them - // We process them sequentially but could parallelize in the future - for (const req of previewRequests) { - try { - // Build preview_creative request for this product - const previewRequest: PreviewCreativeRequest = { - request_type: 'single', - format_id: req.formatId, - creative_manifest: { - format_id: req.formatId, - assets: req.manifest, // manifest contains the asset map (product_image, product_name, etc) - }, - }; - - // Call preview_creative - const response = await creativeAgentClient.previewCreative(previewRequest); - - // Check for data even if validation failed (response.success may be false due to schema warnings) - // Handle both single request (previews) and batch request (results) response formats - const responseData = response.data; - if (responseData && 'previews' in responseData && responseData.previews && responseData.previews.length > 0) { - const preview = responseData.previews[0]!; - if (preview.renders && preview.renders.length > 0) { - const render = preview.renders[0]!; - const previewUrl = - render.output_format === 'url' || render.output_format === 'both' ? render.preview_url : undefined; - const previewId = preview.preview_id; - - if (previewUrl) { - // Cache the result - setCachedPreview(req.cacheKey, previewUrl, previewId); - - results.push({ - item: req.product, - previewUrl, - previewId, - }); - } else { - results.push({ - item: req.product, - error: 'Preview render has no URL', - }); - } - } else { - results.push({ - item: req.product, - error: 'No renders in preview response', - }); - } - } else { - // Only treat as error if we have no data at all - results.push({ - item: req.product, - error: response.error || 'Preview generation failed', - }); - } - } catch (error) { - results.push({ - item: req.product, - error: error instanceof Error ? error.message : 'Unknown error', - }); - } - } - - return results; } /** From 8d67fda42e9182b41d37f38796ac1a4851af038e Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Thu, 21 May 2026 11:34:16 -0400 Subject: [PATCH 2/2] Merge bokelley/cut-8-0-beta: resolve wire-spec-fields timestamp conflict --- node_modules | 1 + schemas/cache | 1 + 2 files changed, 2 insertions(+) create mode 120000 node_modules create mode 120000 schemas/cache diff --git a/node_modules b/node_modules new file mode 120000 index 000000000..8aed18298 --- /dev/null +++ b/node_modules @@ -0,0 +1 @@ +/tmp/wt-8beta-cut/node_modules \ No newline at end of file diff --git a/schemas/cache b/schemas/cache new file mode 120000 index 000000000..3a3d0d99b --- /dev/null +++ b/schemas/cache @@ -0,0 +1 @@ +/tmp/wt-8beta-cut/schemas/cache \ No newline at end of file