diff --git a/.changeset/adcp-v2.6-support.md b/.changeset/adcp-v2.6-support.md new file mode 100644 index 000000000..dfd609607 --- /dev/null +++ b/.changeset/adcp-v2.6-support.md @@ -0,0 +1,11 @@ +--- +"@adcp/client": minor +--- + +Add AdCP v2.6 support with backward compatibility for Format schema changes + +- New `assets` field in Format schema (replaces deprecated `assets_required`) +- Added format-assets utilities: `getFormatAssets()`, `getRequiredAssets()`, `getOptionalAssets()`, etc. +- Updated testing framework to use new utilities +- Added URL input option for image/video assets in testing UI +- Added 21 unit tests for format-assets utilities diff --git a/ADCP_VERSION b/ADCP_VERSION index 92491a0f6..62c36deb2 100644 --- a/ADCP_VERSION +++ b/ADCP_VERSION @@ -1 +1 @@ -v2.5 +v2.6 diff --git a/bin/adcp.js b/bin/adcp.js index d119e7145..31dbbedde 100755 --- a/bin/adcp.js +++ b/bin/adcp.js @@ -14,7 +14,7 @@ * adcp mcp https://agent.example.com/mcp create_media_buy @payload.json --auth $AGENT_TOKEN */ -const { AdCPClient, detectProtocol } = require('../dist/lib/index.js'); +const { AdCPClient, detectProtocol, usesDeprecatedAssetsField } = require('../dist/lib/index.js'); const { readFileSync } = require('fs'); const { AsyncWebhookHandler } = require('./adcp-async-handler.js'); const { getAgent, listAgents, isAlias, interactiveSetup, removeAgent, getConfigPath } = require('./adcp-config.js'); @@ -70,6 +70,18 @@ const BUILT_IN_AGENTS = { }, }; +/** + * Check formats for deprecated assets_required usage + * Returns array of format IDs using the deprecated field + */ +function checkDeprecatedFormats(formats) { + if (!formats || !Array.isArray(formats)) return []; + + return formats + .filter(format => usesDeprecatedAssetsField(format)) + .map(format => format.format_id?.id || format.format_id || format.id || format.name || 'unknown'); +} + /** * Extract human-readable protocol message from conversation */ @@ -858,10 +870,24 @@ async function main() { } } + // Check for deprecated assets_required usage in list_creative_formats response + let deprecationWarnings = []; + if (toolName === 'list_creative_formats' && result.success && result.data) { + const formats = result.data.formats || result.data; + const deprecatedFormats = checkDeprecatedFormats(formats); + if (deprecatedFormats.length > 0) { + deprecationWarnings.push({ + type: 'assets_required_deprecated', + message: `āš ļø DEPRECATION: ${deprecatedFormats.length} format(s) using deprecated 'assets_required' field. Please migrate to use 'assets' instead.`, + formats: deprecatedFormats, + }); + } + } + // Handle result if (result.success) { if (jsonOutput) { - // Raw JSON output - include protocol metadata + // Raw JSON output - include protocol metadata and warnings console.log( JSON.stringify( { @@ -876,6 +902,7 @@ async function main() { contextId: result.metadata.taskId, // Using taskId as context identifier }), }, + ...(deprecationWarnings.length > 0 && { warnings: deprecationWarnings }), }, null, 2 @@ -885,6 +912,19 @@ async function main() { // Pretty output console.log('\nāœ… SUCCESS\n'); + // Show deprecation warnings if any + if (deprecationWarnings.length > 0) { + for (const warning of deprecationWarnings) { + console.log(warning.message); + if (warning.formats && warning.formats.length > 0) { + const displayFormats = warning.formats.slice(0, 5).join(', '); + const remaining = warning.formats.length - 5; + console.log(` Affected formats: ${displayFormats}${remaining > 0 ? `, (+${remaining} more)` : ''}`); + } + console.log(''); + } + } + // Show protocol message if available if (result.conversation && result.conversation.length > 0) { const message = extractProtocolMessage(result.conversation, result.metadata.agent.protocol); diff --git a/examples/inspect-card-formats.ts b/examples/inspect-card-formats.ts index d882ab7a4..8f747e80a 100755 --- a/examples/inspect-card-formats.ts +++ b/examples/inspect-card-formats.ts @@ -5,11 +5,12 @@ * * This will help us understand: * 1. What format IDs are available for cards (product_card, format_card) - * 2. What assets_required those formats expect + * 2. What assets those formats expect (using new `assets` field or deprecated `assets_required`) * 3. How to properly structure creative_manifest for cards */ import { AdCPClient } from '../src/lib/core/AdCPClient'; +import { getFormatAssets, getRequiredAssets, getOptionalAssets, usesDeprecatedAssetsField } from '../src/lib'; const CREATIVE_AGENT_URL = process.env.CREATIVE_AGENT_URL || 'https://creative.adcontextprotocol.org/mcp'; const CREATIVE_AGENT_PROTOCOL = (process.env.CREATIVE_AGENT_PROTOCOL || 'mcp') as 'mcp' | 'a2a'; @@ -66,21 +67,35 @@ async function main() { console.log(`Type: ${format.type || 'N/A'}`); console.log(`Description: ${format.description || 'N/A'}`); - if (format.assets_required && format.assets_required.length > 0) { - console.log('\nšŸ“¦ Required Assets:'); - format.assets_required.forEach(asset => { - console.log(`\n Asset: ${asset.asset_id}`); - console.log(` Type: ${asset.asset_type}`); - console.log(` Required: ${asset.required !== false ? 'yes' : 'no'}`); - if (asset.description) { - console.log(` Description: ${asset.description}`); - } - if (asset.default_value) { - console.log(` Default: ${JSON.stringify(asset.default_value)}`); + const assets = getFormatAssets(format); + if (assets.length > 0) { + const requiredAssets = getRequiredAssets(format); + const optionalAssets = getOptionalAssets(format); + console.log( + `\nšŸ“¦ Assets: ${assets.length} total (${requiredAssets.length} required, ${optionalAssets.length} optional)` + ); + if (usesDeprecatedAssetsField(format)) { + console.log(' āš ļø Using deprecated assets_required field'); + } + assets.forEach(asset => { + if (asset.item_type === 'individual') { + console.log(`\n Asset: ${asset.asset_id}`); + console.log(` Type: ${asset.asset_type}`); + console.log(` Required: ${asset.required ? 'yes' : 'no'}`); + if ((asset as any).description) { + console.log(` Description: ${(asset as any).description}`); + } + if ((asset as any).default_value) { + console.log(` Default: ${JSON.stringify((asset as any).default_value)}`); + } + } else { + console.log(`\n Asset Group: ${asset.asset_group_id}`); + console.log(` Required: ${asset.required ? 'yes' : 'no'}`); + console.log(` Count: ${asset.min_count}-${asset.max_count}`); } }); } else { - console.log('\nāš ļø No assets_required defined (flexible format)'); + console.log('\nāš ļø No assets defined (flexible format)'); } if (format.preview_image) { @@ -107,10 +122,24 @@ async function main() { console.log(`Name: ${displayFormat.name || 'Unnamed'}`); console.log(`Type: ${displayFormat.type || 'N/A'}`); - if (displayFormat.assets_required && displayFormat.assets_required.length > 0) { - console.log('\nšŸ“¦ Required Assets:'); - displayFormat.assets_required.forEach(asset => { - console.log(` - ${asset.asset_id} (${asset.asset_type})${asset.required !== false ? ' *required*' : ''}`); + const displayAssets = getFormatAssets(displayFormat); + if (displayAssets.length > 0) { + const requiredAssets = getRequiredAssets(displayFormat); + const optionalAssets = getOptionalAssets(displayFormat); + console.log( + `\nšŸ“¦ Assets: ${displayAssets.length} total (${requiredAssets.length} required, ${optionalAssets.length} optional)` + ); + if (usesDeprecatedAssetsField(displayFormat)) { + console.log(' āš ļø Using deprecated assets_required field'); + } + displayAssets.forEach(asset => { + if (asset.item_type === 'individual') { + console.log(` - ${asset.asset_id} (${asset.asset_type})${asset.required ? ' *required*' : ' (optional)'}`); + } else { + console.log( + ` - [Group] ${asset.asset_group_id} (${asset.min_count}-${asset.max_count})${asset.required ? ' *required*' : ' (optional)'}` + ); + } }); } } else { @@ -119,10 +148,11 @@ async function main() { console.log('\n\nšŸ’” Key Insights:'); console.log('─'.repeat(60)); - console.log('1. Card formats should define their assets_required'); - console.log('2. creative_manifest.assets should map asset_id -> asset object'); - console.log("3. Each asset_id must match the format's assets_required"); - console.log('4. Asset types (ImageAsset, TextAsset, etc) depend on asset_type\n'); + console.log('1. Formats define their assets using `assets` field (v2.6+) or deprecated `assets_required`'); + console.log('2. Use getFormatAssets() helper to access assets with backward compatibility'); + console.log('3. creative_manifest.assets should map asset_id -> asset object'); + console.log("4. Each asset_id must match the format's assets definition"); + console.log('5. Asset types (ImageAsset, TextAsset, etc) depend on asset_type\n'); } main().catch(error => { diff --git a/package-lock.json b/package-lock.json index c791c76bd..1622ffe62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@adcp/client", - "version": "3.5.2", + "version": "3.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adcp/client", - "version": "3.5.2", + "version": "3.6.0", "license": "MIT", "dependencies": { "better-sqlite3": "^12.4.1", diff --git a/package.json b/package.json index f9e43a0b1..53ba31465 100644 --- a/package.json +++ b/package.json @@ -153,5 +153,5 @@ "js-yaml": "^3.14.1" } }, - "adcp_version": "v2.5" + "adcp_version": "v2.6" } diff --git a/src/lib/core/AsyncHandler.ts b/src/lib/core/AsyncHandler.ts index 1f837f921..d8f6bdf47 100644 --- a/src/lib/core/AsyncHandler.ts +++ b/src/lib/core/AsyncHandler.ts @@ -240,7 +240,7 @@ export class AsyncHandler { typeof result === 'object' && 'notification_type' in result ) { - const notificationPayload = result as MediaBuyDeliveryNotification; + const notificationPayload = result as unknown as MediaBuyDeliveryNotification; // Build notification metadata // operation_id comes from webhook URL and was lazily generated from agent + month diff --git a/src/lib/index.ts b/src/lib/index.ts index 9b01dfbc3..9e4850152 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -174,6 +174,20 @@ export { getStandardFormats, unwrapProtocolResponse, isAdcpError, isAdcpSuccess export { REQUEST_TIMEOUT, MAX_CONCURRENT, STANDARD_FORMATS } from './utils'; export { detectProtocol, detectProtocolWithTimeout } from './utils'; +// ====== FORMAT ASSET UTILITIES ====== +// Backward-compatible access to format assets (v2.6 `assets` field replaces deprecated `assets_required`) +export { + getFormatAssets, + normalizeAssetsRequired, + getRequiredAssets, + getOptionalAssets, + getIndividualAssets, + getRepeatableGroups, + usesDeprecatedAssetsField, + getAssetCount, + hasAssets, +} from './utils/format-assets'; + // ====== TYPE GUARD UTILITIES ====== // Type guards for automatic TypeScript type narrowing in webhook handlers export { diff --git a/src/lib/testing/client.ts b/src/lib/testing/client.ts index 3202d5906..362b745c7 100644 --- a/src/lib/testing/client.ts +++ b/src/lib/testing/client.ts @@ -3,6 +3,7 @@ */ import { ADCPMultiAgentClient } from '../core/ADCPMultiAgentClient'; +import { getFormatAssets, usesDeprecatedAssetsField } from '../utils/format-assets'; import type { TestOptions, TestStepResult, AgentProfile, TaskResult, Logger } from './types'; // Default console-based logger @@ -263,6 +264,7 @@ export async function discoverCreativeFormats( if (result?.success && result?.data) { const data = result.data as any; const rawFormats = data.formats || data.format_ids || []; + const deprecatedFormats: string[] = []; for (const format of rawFormats) { const formatInfo: NonNullable[0] = { @@ -273,14 +275,21 @@ export async function discoverCreativeFormats( optional_assets: [], }; - // Extract asset requirements from format spec - if (format.asset_slots) { - for (const slot of format.asset_slots) { - if (slot.required) { - formatInfo.required_assets?.push(slot.name || slot.id); - } else { - formatInfo.optional_assets?.push(slot.name || slot.id); - } + // Check for deprecated assets_required usage + if (usesDeprecatedAssetsField(format)) { + deprecatedFormats.push(formatInfo.format_id); + } + + // Extract asset requirements from format spec using format-assets utilities + // This handles both v2.6 `assets` and deprecated `assets_required` fields + const formatAssets = getFormatAssets(format); + for (const asset of formatAssets) { + const assetId = asset.item_type === 'individual' ? asset.asset_id : asset.asset_group_id; + + if (asset.required) { + formatInfo.required_assets?.push(assetId); + } else { + formatInfo.optional_assets?.push(assetId); } } @@ -301,6 +310,17 @@ export async function discoverCreativeFormats( null, 2 ); + + // Add deprecation warnings if any formats use assets_required + if (deprecatedFormats.length > 0) { + step.warnings = [ + `āš ļø DEPRECATION: ${deprecatedFormats.length} format(s) use 'assets_required' field which is deprecated and will be removed in a future version. Please migrate to the 'assets' field instead. (adcp-client 3.6.0+)`, + ]; + logger.warn( + { deprecated_formats: deprecatedFormats }, + `Agent uses deprecated 'assets_required' field in ${deprecatedFormats.length} format(s). Migrate to 'assets' field.` + ); + } } else if (result && !result.success) { step.passed = false; step.error = result.error || `${toolName} failed`; diff --git a/src/lib/testing/formatter.ts b/src/lib/testing/formatter.ts index 3fbae0c7c..cedde9046 100644 --- a/src/lib/testing/formatter.ts +++ b/src/lib/testing/formatter.ts @@ -50,6 +50,12 @@ export function formatTestResults(result: TestResult): string { output += ` ${step.details}\n`; } + if (step.warnings && step.warnings.length > 0) { + for (const warning of step.warnings) { + output += ` ${warning}\n`; + } + } + if (step.error) { output += ` āš ļø Error: ${step.error}\n`; } @@ -83,6 +89,14 @@ export function formatTestResultsSummary(result: TestResult): string { const statusEmoji = result.overall_passed ? 'āœ…' : 'āŒ'; const passedCount = result.steps.filter(s => s.passed).length; const failedCount = result.steps.filter(s => !s.passed).length; + const warningCount = result.steps.filter(s => s.warnings && s.warnings.length > 0).length; - return `${statusEmoji} ${result.scenario}: ${passedCount}/${result.steps.length} passed (${result.total_duration_ms}ms)${failedCount > 0 ? ` - ${failedCount} failed` : ''}`; + let summary = `${statusEmoji} ${result.scenario}: ${passedCount}/${result.steps.length} passed (${result.total_duration_ms}ms)`; + if (failedCount > 0) { + summary += ` - ${failedCount} failed`; + } + if (warningCount > 0) { + summary += ` - āš ļø ${warningCount} warning(s)`; + } + return summary; } diff --git a/src/lib/testing/types.ts b/src/lib/testing/types.ts index f52e6d992..7ef708f07 100644 --- a/src/lib/testing/types.ts +++ b/src/lib/testing/types.ts @@ -80,6 +80,8 @@ export interface TestStepResult { response_preview?: string; // For tracking what was created (for cleanup or follow-up) created_id?: string; + // Deprecation or other warnings + warnings?: string[]; } export interface AgentProfile { diff --git a/src/lib/types/core.generated.ts b/src/lib/types/core.generated.ts index e8d76452e..7f8ef2e55 100644 --- a/src/lib/types/core.generated.ts +++ b/src/lib/types/core.generated.ts @@ -1,5 +1,5 @@ -// Generated AdCP core types from official schemas v2.5.1 -// Generated at: 2026-01-04T17:26:07.039Z +// Generated AdCP core types from official schemas v2.6.0 +// Generated at: 2026-01-08T00:11:09.339Z // MEDIA-BUY SCHEMA /** @@ -97,6 +97,7 @@ export interface Package { */ paused?: boolean; ext?: ExtensionObject; + [k: string]: unknown; } /** * Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance). @@ -127,6 +128,7 @@ export interface TargetingOverlay { */ axe_exclude_segment?: string; frequency_cap?: FrequencyCap; + [k: string]: unknown; } /** * Frequency capping settings for package-level application @@ -136,6 +138,7 @@ export interface FrequencyCap { * Minutes to suppress after impression */ suppress_minutes: number; + [k: string]: unknown; } /** * Assignment of a creative asset to a package with optional placement targeting. Used in create_media_buy and update_media_buy requests. Note: sync_creatives does not support placement_ids - use create/update_media_buy for placement-level targeting. @@ -155,6 +158,7 @@ export interface CreativeAssignment { * @minItems 1 */ placement_ids?: [string, ...string[]]; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -180,6 +184,7 @@ export interface FormatID { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization. @@ -219,6 +224,7 @@ export type VASTAsset = * Tracking events supported by this VAST tag */ tracking_events?: VASTTrackingEvent[]; + [k: string]: unknown; } | { /** @@ -242,6 +248,7 @@ export type VASTAsset = * Tracking events supported by this VAST tag */ tracking_events?: VASTTrackingEvent[]; + [k: string]: unknown; }; /** * VAST specification version @@ -297,6 +304,7 @@ export type DAASTAsset = * Whether companion display ads are included */ companion_ads?: boolean; + [k: string]: unknown; } | { /** @@ -320,6 +328,7 @@ export type DAASTAsset = * Whether companion display ads are included */ companion_ads?: boolean; + [k: string]: unknown; }; /** * DAAST specification version @@ -464,6 +473,7 @@ export interface ImageAsset { * Alternative text for accessibility */ alt_text?: string; + [k: string]: unknown; } /** * Video asset with URL and specifications @@ -493,6 +503,7 @@ export interface VideoAsset { * Video bitrate in kilobits per second */ bitrate_kbps?: number; + [k: string]: unknown; } /** * Audio asset with URL and specifications @@ -514,6 +525,7 @@ export interface AudioAsset { * Audio bitrate in kilobits per second */ bitrate_kbps?: number; + [k: string]: unknown; } /** * Text content asset @@ -527,6 +539,7 @@ export interface TextAsset { * Language code (e.g., 'en', 'es', 'fr') */ language?: string; + [k: string]: unknown; } /** * HTML content asset @@ -540,6 +553,7 @@ export interface HTMLAsset { * HTML version (e.g., 'HTML5') */ version?: string; + [k: string]: unknown; } /** * CSS stylesheet asset @@ -553,6 +567,7 @@ export interface CSSAsset { * CSS media query context (e.g., 'screen', 'print') */ media?: string; + [k: string]: unknown; } /** * JavaScript code asset @@ -563,6 +578,7 @@ export interface JavaScriptAsset { */ content: string; module_type?: JavaScriptModuleType; + [k: string]: unknown; } /** * Complete offering specification combining brand manifest, product selectors, and asset filters. Provides all context needed for creative generation about what is being promoted. @@ -588,6 +604,7 @@ export interface PromotedOfferings { assets?: { [k: string]: unknown; }[]; + [k: string]: unknown; }[]; /** * Selectors to choose specific assets from the brand manifest @@ -617,7 +634,9 @@ export interface PromotedOfferings { * Exclude assets with these tags */ exclude_tags?: string[]; + [k: string]: unknown; }; + [k: string]: unknown; } /** * Inline brand manifest object @@ -753,6 +772,7 @@ export interface BrandManifest { metadata?: { [k: string]: unknown; }; + [k: string]: unknown; }[]; /** * Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection. @@ -778,6 +798,7 @@ export interface BrandManifest { * How frequently the product catalog is updated */ update_frequency?: 'realtime' | 'hourly' | 'daily' | 'weekly'; + [k: string]: unknown; }; /** * Legal disclaimers or required text that must appear in creatives @@ -834,6 +855,7 @@ export interface BrandManifest { */ version?: string; }; + [k: string]: unknown; } /** * Selectors to choose which products/offerings from the brand manifest product catalog to promote @@ -855,6 +877,7 @@ export interface PromotedProducts { * Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20') */ manifest_query?: string; + [k: string]: unknown; } /** * URL reference asset @@ -869,6 +892,7 @@ export interface URLAsset { * Description of what this URL points to */ description?: string; + [k: string]: unknown; } // PRODUCT SCHEMA @@ -885,6 +909,7 @@ export type PublisherPropertySelector = * Discriminator indicating all properties from this publisher are included */ selection_type: 'all'; + [k: string]: unknown; } | { /** @@ -901,6 +926,7 @@ export type PublisherPropertySelector = * @minItems 1 */ property_ids: [PropertyID, ...PropertyID[]]; + [k: string]: unknown; } | { /** @@ -917,6 +943,7 @@ export type PublisherPropertySelector = * @minItems 1 */ property_tags: [PropertyTag, ...PropertyTag[]]; + [k: string]: unknown; }; /** * Identifier for a publisher property. Must be lowercase alphanumeric with underscores only. @@ -1078,6 +1105,7 @@ export interface Placement { * @minItems 1 */ format_ids?: [FormatID, ...FormatID[]]; + [k: string]: unknown; } /** * Cost Per Mille (cost per 1,000 impressions) with guaranteed fixed rate - common for direct/guaranteed deals @@ -1107,6 +1135,7 @@ export interface CPMFixedRatePricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Mille (cost per 1,000 impressions) with auction-based pricing - common for programmatic/non-guaranteed inventory @@ -1157,6 +1186,7 @@ export interface CPMAuctionPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Viewable Cost Per Mille (cost per 1,000 viewable impressions) with guaranteed fixed rate - impressions meeting MRC viewability standard (50% pixels in-view for 1 second for display, 2 seconds for video) @@ -1186,6 +1216,7 @@ export interface VCPMFixedRatePricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Viewable Cost Per Mille (cost per 1,000 viewable impressions) with auction-based pricing - impressions meeting MRC viewability standard (50% pixels in-view for 1 second for display, 2 seconds for video) @@ -1236,6 +1267,7 @@ export interface VCPMAuctionPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Click fixed-rate pricing for performance-driven advertising campaigns @@ -1265,6 +1297,7 @@ export interface CPCPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Completed View (100% video/audio completion) fixed-rate pricing @@ -1294,6 +1327,7 @@ export interface CPCVPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per View (at publisher-defined threshold) fixed-rate pricing for video/audio @@ -1330,12 +1364,15 @@ export interface CPVPricingOption { * Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view') */ duration_seconds: number; + [k: string]: unknown; }; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Point (Gross Rating Point) fixed-rate pricing for TV and audio campaigns requiring demographic measurement @@ -1373,11 +1410,13 @@ export interface CPPPricingOption { * Minimum GRPs/TRPs required for this pricing option */ min_points?: number; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Flat rate pricing for DOOH, sponsorships, and time-based campaigns - fixed cost regardless of delivery volume @@ -1435,11 +1474,13 @@ export interface FlatRatePricingOption { * Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight') */ daypart?: string; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Measurement capabilities included with a product @@ -1461,6 +1502,7 @@ export interface Measurement { * Reporting frequency and format */ reporting: string; + [k: string]: unknown; } /** * Reporting capabilities available for a product @@ -1488,6 +1530,7 @@ export interface ReportingCapabilities { * Metrics available in reporting. Impressions and spend are always implicitly included. */ available_metrics: AvailableMetric[]; + [k: string]: unknown; } /** * Creative requirements and restrictions for a product @@ -1499,6 +1542,7 @@ export interface CreativePolicy { * Whether creative templates are provided */ templates_available: boolean; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1524,6 +1568,7 @@ export interface FormatID1 { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1549,6 +1594,7 @@ export interface FormatID2 { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization. @@ -1717,6 +1763,7 @@ export interface GetProductsResponse { errors?: Error[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Represents available advertising inventory @@ -1748,6 +1795,7 @@ export interface Error { details?: { [k: string]: unknown; }; + [k: string]: unknown; } /** * Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned. @@ -1777,6 +1825,7 @@ export interface GetProductsAsyncWorking { step_number?: number; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Input requirements for get_products needing clarification @@ -1796,6 +1845,7 @@ export interface GetProductsAsyncInputRequired { suggestions?: string[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Acknowledgment for submitted get_products (custom curation) @@ -1807,6 +1857,7 @@ export interface GetProductsAsyncSubmitted { estimated_completion?: string; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Success response - media buy created successfully @@ -1830,6 +1881,7 @@ export interface CreateMediaBuySuccess { packages: Package[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * A specific product within a media buy (line item) @@ -1843,6 +1895,7 @@ export interface CreateMediaBuyError { errors: [Error, ...Error[]]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Progress data for working create_media_buy @@ -1866,6 +1919,7 @@ export interface CreateMediaBuyAsyncWorking { step_number?: number; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Input requirements for create_media_buy needing user input @@ -1881,6 +1935,7 @@ export interface CreateMediaBuyAsyncInputRequired { errors?: Error[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Acknowledgment for submitted create_media_buy @@ -1888,6 +1943,7 @@ export interface CreateMediaBuyAsyncInputRequired { export interface CreateMediaBuyAsyncSubmitted { context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Success response - media buy updated successfully @@ -1911,6 +1967,7 @@ export interface UpdateMediaBuySuccess { affected_packages?: Package[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Error response - operation failed, no changes applied @@ -1924,6 +1981,7 @@ export interface UpdateMediaBuyError { errors: [Error, ...Error[]]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Progress data for working update_media_buy @@ -1947,6 +2005,7 @@ export interface UpdateMediaBuyAsyncWorking { step_number?: number; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Input requirements for update_media_buy needing user input @@ -1958,6 +2017,7 @@ export interface UpdateMediaBuyAsyncInputRequired { reason?: 'APPROVAL_REQUIRED' | 'CHANGE_CONFIRMATION'; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Acknowledgment for submitted update_media_buy @@ -1965,6 +2025,7 @@ export interface UpdateMediaBuyAsyncInputRequired { export interface UpdateMediaBuyAsyncSubmitted { context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Success response - sync operation processed creatives (may include per-item failures) @@ -2015,17 +2076,13 @@ export interface SyncCreativesSuccess { * Assignment errors by package ID (only present when assignment failures occurred) */ assignment_errors?: { - /** - * Error message for this package assignment - * - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` "^[a-zA-Z0-9_-]+$". - */ - [k: string]: string; + [k: string]: unknown; }; + [k: string]: unknown; }[]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Error response - operation failed completely, no creatives were processed @@ -2039,6 +2096,7 @@ export interface SyncCreativesError { errors: [Error, ...Error[]]; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Progress data for working sync_creatives @@ -2070,6 +2128,7 @@ export interface SyncCreativesAsyncWorking { creatives_total?: number; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Input requirements for sync_creatives needing user input @@ -2081,6 +2140,7 @@ export interface SyncCreativesAsyncInputRequired { reason?: 'APPROVAL_REQUIRED' | 'ASSET_CONFIRMATION' | 'FORMAT_CLARIFICATION'; context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } /** * Acknowledgment for submitted sync_creatives @@ -2088,5 +2148,6 @@ export interface SyncCreativesAsyncInputRequired { export interface SyncCreativesAsyncSubmitted { context?: ContextObject; ext?: ExtensionObject; + [k: string]: unknown; } diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index 4f00b9a0b..d3b612a8d 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-01-04T17:26:07.781Z +// Generated at: 2026-01-08T00:11:10.008Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -16,23 +16,23 @@ export const PacingSchema = z.union([z.literal("even"), z.literal("asap"), z.lit export const ExtensionObjectSchema = z.record(z.string(), z.unknown()); -export const CreativeAssignmentSchema = z.object({ +export const CreativeAssignmentSchema = z.record(z.string(), z.unknown()).and(z.object({ creative_id: z.string(), weight: z.number().nullish(), placement_ids: z.array(z.string()).nullish() -}); +})); -export const FormatIDSchema = z.object({ +export const FormatIDSchema = z.record(z.string(), z.unknown()).and(z.object({ agent_url: z.string(), id: z.string(), width: z.number().nullish(), height: z.number().nullish(), duration_ms: z.number().nullish() -}); +})); -export const FrequencyCapSchema = z.object({ +export const FrequencyCapSchema = z.record(z.string(), z.unknown()).and(z.object({ suppress_minutes: z.number() -}); +})); export const VASTVersionSchema = z.union([z.literal("2.0"), z.literal("3.0"), z.literal("4.0"), z.literal("4.1"), z.literal("4.2")]); @@ -46,85 +46,85 @@ export const DAASTTrackingEventSchema = z.union([z.literal("start"), z.literal(" export const DAASTVersion1Schema = z.union([z.literal("1.0"), z.literal("1.1")]); -export const ImageAssetSchema = z.object({ +export const ImageAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string(), width: z.number(), height: z.number(), format: z.string().nullish(), alt_text: z.string().nullish() -}); +})); -export const VideoAssetSchema = z.object({ +export const VideoAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string(), width: z.number(), height: z.number(), duration_ms: z.number().nullish(), format: z.string().nullish(), bitrate_kbps: z.number().nullish() -}); +})); -export const AudioAssetSchema = z.object({ +export const AudioAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string(), duration_ms: z.number().nullish(), format: z.string().nullish(), bitrate_kbps: z.number().nullish() -}); +})); -export const TextAssetSchema = z.object({ +export const TextAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ content: z.string(), language: z.string().nullish() -}); +})); -export const HTMLAssetSchema = z.object({ +export const HTMLAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ content: z.string(), version: z.string().nullish() -}); +})); -export const CSSAssetSchema = z.object({ +export const CSSAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ content: z.string(), media: z.string().nullish() -}); +})); -export const VASTAssetSchema = z.union([z.object({ +export const VASTAssetSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ delivery_type: z.literal("url"), url: z.string(), vast_version: VASTVersionSchema.nullish(), vpaid_enabled: z.boolean().nullish(), duration_ms: z.number().nullish(), tracking_events: z.array(VASTTrackingEventSchema).nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ delivery_type: z.literal("inline"), content: z.string(), vast_version: VASTVersion1Schema.nullish(), vpaid_enabled: z.boolean().nullish(), duration_ms: z.number().nullish(), tracking_events: z.array(VASTTrackingEventSchema).nullish() - })]); + }))]); -export const DAASTAssetSchema = z.union([z.object({ +export const DAASTAssetSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ delivery_type: z.literal("url"), url: z.string(), daast_version: DAASTVersionSchema.nullish(), duration_ms: z.number().nullish(), tracking_events: z.array(DAASTTrackingEventSchema).nullish(), companion_ads: z.boolean().nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ delivery_type: z.literal("inline"), content: z.string(), daast_version: DAASTVersion1Schema.nullish(), duration_ms: z.number().nullish(), tracking_events: z.array(DAASTTrackingEventSchema).nullish(), companion_ads: z.boolean().nullish() - })]); + }))]); export const JavaScriptModuleTypeSchema = z.union([z.literal("esm"), z.literal("commonjs"), z.literal("script")]); -export const PromotedProductsSchema = z.object({ +export const PromotedProductsSchema = z.record(z.string(), z.unknown()).and(z.object({ manifest_skus: z.array(z.string()).nullish(), manifest_tags: z.array(z.string()).nullish(), manifest_category: z.string().nullish(), manifest_query: z.string().nullish() -}); +})); export const AssetContentTypeSchema = z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("markdown"), z.literal("html"), z.literal("css"), z.literal("javascript"), z.literal("vast"), z.literal("daast"), z.literal("promoted_offerings"), z.literal("url"), z.literal("webhook")]); @@ -132,16 +132,16 @@ export const URLAssetTypeSchema = z.union([z.literal("clickthrough"), z.literal( export const DeliveryTypeSchema = z.union([z.literal("guaranteed"), z.literal("non_guaranteed")]); -export const CPMFixedRatePricingOptionSchema = z.object({ +export const CPMFixedRatePricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpm"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), min_spend_per_package: z.number().nullish() -}); +})); -export const CPMAuctionPricingOptionSchema = z.object({ +export const CPMAuctionPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpm"), currency: z.string(), @@ -154,18 +154,18 @@ export const CPMAuctionPricingOptionSchema = z.object({ p90: z.number().nullish() }), min_spend_per_package: z.number().nullish() -}); +})); -export const VCPMFixedRatePricingOptionSchema = z.object({ +export const VCPMFixedRatePricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("vcpm"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), min_spend_per_package: z.number().nullish() -}); +})); -export const VCPMAuctionPricingOptionSchema = z.object({ +export const VCPMAuctionPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("vcpm"), currency: z.string(), @@ -178,60 +178,60 @@ export const VCPMAuctionPricingOptionSchema = z.object({ p90: z.number().nullish() }), min_spend_per_package: z.number().nullish() -}); +})); -export const CPCPricingOptionSchema = z.object({ +export const CPCPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpc"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), min_spend_per_package: z.number().nullish() -}); +})); -export const CPCVPricingOptionSchema = z.object({ +export const CPCVPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpcv"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), min_spend_per_package: z.number().nullish() -}); +})); -export const CPVPricingOptionSchema = z.object({ +export const CPVPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpv"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), - parameters: z.object({ - view_threshold: z.union([z.number(), z.object({ + parameters: z.record(z.string(), z.unknown()).and(z.object({ + view_threshold: z.union([z.number(), z.record(z.string(), z.unknown()).and(z.object({ duration_seconds: z.number() - })]) - }), + }))]) + })), min_spend_per_package: z.number().nullish() -}); +})); -export const CPPPricingOptionSchema = z.object({ +export const CPPPricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("cpp"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), - parameters: z.object({ + parameters: z.record(z.string(), z.unknown()).and(z.object({ demographic: z.string(), min_points: z.number().nullish() - }), + })), min_spend_per_package: z.number().nullish() -}); +})); -export const FlatRatePricingOptionSchema = z.object({ +export const FlatRatePricingOptionSchema = z.record(z.string(), z.unknown()).and(z.object({ pricing_option_id: z.string(), pricing_model: z.literal("flat_rate"), rate: z.number(), currency: z.string(), is_fixed: z.literal(true), - parameters: z.object({ + parameters: z.record(z.string(), z.unknown()).and(z.object({ duration_hours: z.number().nullish(), sov_percentage: z.number().nullish(), loop_duration_seconds: z.number().nullish(), @@ -239,41 +239,41 @@ export const FlatRatePricingOptionSchema = z.object({ venue_package: z.string().nullish(), estimated_impressions: z.number().nullish(), daypart: z.string().nullish() - }).nullish(), + })).nullish(), min_spend_per_package: z.number().nullish() -}); +})); -export const PlacementSchema = z.object({ +export const PlacementSchema = z.record(z.string(), z.unknown()).and(z.object({ placement_id: z.string(), name: z.string(), description: z.string().nullish(), format_ids: z.array(FormatIDSchema).nullish() -}); +})); export const PricingOptionSchema = z.union([CPMFixedRatePricingOptionSchema, CPMAuctionPricingOptionSchema, VCPMFixedRatePricingOptionSchema, VCPMAuctionPricingOptionSchema, CPCPricingOptionSchema, CPCVPricingOptionSchema, CPVPricingOptionSchema, CPPPricingOptionSchema, FlatRatePricingOptionSchema]); -export const MeasurementSchema = z.object({ +export const MeasurementSchema = z.record(z.string(), z.unknown()).and(z.object({ type: z.string(), attribution: z.string(), window: z.string().nullish(), reporting: z.string() -}); +})); -export const FormatID1Schema = z.object({ +export const FormatID1Schema = z.record(z.string(), z.unknown()).and(z.object({ agent_url: z.string(), id: z.string(), width: z.number().nullish(), height: z.number().nullish(), duration_ms: z.number().nullish() -}); +})); -export const FormatID2Schema = z.object({ +export const FormatID2Schema = z.record(z.string(), z.unknown()).and(z.object({ agent_url: z.string(), id: z.string(), width: z.number().nullish(), height: z.number().nullish(), duration_ms: z.number().nullish() -}); +})); export const ReportingFrequencySchema = z.union([z.literal("hourly"), z.literal("daily"), z.literal("monthly")]); @@ -293,14 +293,14 @@ export const PropertyTagSchema = z.string(); export const CreativeActionSchema = z.union([z.literal("created"), z.literal("updated"), z.literal("unchanged"), z.literal("failed"), z.literal("deleted")]); -export const ErrorSchema = z.object({ +export const ErrorSchema = z.record(z.string(), z.unknown()).and(z.object({ code: z.string(), message: z.string(), field: z.string().nullish(), suggestion: z.string().nullish(), retry_after: z.number().nullish(), details: z.record(z.string(), z.unknown()).nullish() -}); +})); export const ContextObjectSchema = z.record(z.string(), z.unknown()); @@ -328,7 +328,7 @@ export const SyncCreativesErrorSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const BrandManifestSchema = z.object({ +export const BrandManifestSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string().nullish(), name: z.string(), logos: z.array(z.object({ @@ -351,7 +351,7 @@ export const BrandManifestSchema = z.object({ }).nullish(), tone: z.string().nullish(), tagline: z.string().nullish(), - assets: z.array(z.object({ + assets: z.array(z.record(z.string(), z.unknown()).and(z.object({ asset_id: z.string(), asset_type: AssetContentTypeSchema, url: z.string(), @@ -364,14 +364,14 @@ export const BrandManifestSchema = z.object({ file_size_bytes: z.number().nullish(), format: z.string().nullish(), metadata: z.record(z.string(), z.unknown()).nullish() - })).nullish(), - product_catalog: z.object({ + }))).nullish(), + product_catalog: z.record(z.string(), z.unknown()).and(z.object({ feed_url: z.string(), feed_format: z.union([z.literal("google_merchant_center"), z.literal("facebook_catalog"), z.literal("custom")]).nullish(), categories: z.array(z.string()).nullish(), last_updated: z.string().nullish(), update_frequency: z.union([z.literal("realtime"), z.literal("hourly"), z.literal("daily"), z.literal("weekly")]).nullish() - }).nullish(), + })).nullish(), disclaimers: z.array(z.object({ text: z.string(), context: z.string().nullish(), @@ -388,36 +388,36 @@ export const BrandManifestSchema = z.object({ updated_date: z.string().nullish(), version: z.string().nullish() }).nullish() -}); +})); export const BrandManifestReferenceSchema = z.union([BrandManifestSchema, z.string()]); -export const PublisherPropertySelectorSchema = z.union([z.object({ +export const PublisherPropertySelectorSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ publisher_domain: z.string(), selection_type: z.literal("all") - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ publisher_domain: z.string(), selection_type: z.literal("by_id"), property_ids: z.array(PropertyIDSchema) - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ publisher_domain: z.string(), selection_type: z.literal("by_tag"), property_tags: z.array(PropertyTagSchema) - })]); + }))]); -export const ReportingCapabilitiesSchema = z.object({ +export const ReportingCapabilitiesSchema = z.record(z.string(), z.unknown()).and(z.object({ available_reporting_frequencies: z.array(ReportingFrequencySchema), expected_delay_minutes: z.number(), timezone: z.string(), supports_webhooks: z.boolean(), available_metrics: z.array(AvailableMetricSchema) -}); +})); -export const CreativePolicySchema = z.object({ +export const CreativePolicySchema = z.record(z.string(), z.unknown()).and(z.object({ co_branding: CoBrandingRequirementSchema, landing_page: LandingPageRequirementSchema, templates_available: z.boolean() -}); +})); export const FormatCategorySchema = z.union([z.literal("audio"), z.literal("video"), z.literal("display"), z.literal("native"), z.literal("dooh"), z.literal("rich_media"), z.literal("universal")]); @@ -427,13 +427,17 @@ export const FormatIDParameterSchema = z.union([z.literal("dimensions"), z.liter export const AssetContentType1Schema = z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("markdown"), z.literal("html"), z.literal("css"), z.literal("javascript"), z.literal("vast"), z.literal("daast"), z.literal("promoted_offerings"), z.literal("url"), z.literal("webhook")]); -export const FormatID3Schema = z.object({ +export const AssetContentType2Schema = z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("markdown"), z.literal("html"), z.literal("css"), z.literal("javascript"), z.literal("vast"), z.literal("daast"), z.literal("promoted_offerings"), z.literal("url"), z.literal("webhook")]); + +export const AssetContentType3Schema = z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("markdown"), z.literal("html"), z.literal("css"), z.literal("javascript"), z.literal("vast"), z.literal("daast"), z.literal("promoted_offerings"), z.literal("url"), z.literal("webhook")]); + +export const FormatID3Schema = z.record(z.string(), z.unknown()).and(z.object({ agent_url: z.string(), id: z.string(), width: z.number().nullish(), height: z.number().nullish(), duration_ms: z.number().nullish() -}); +})); export const BrandManifestReference1Schema = z.union([BrandManifestSchema, z.string()]); @@ -441,7 +445,7 @@ export const StartTimingSchema = z.union([z.literal("asap"), z.string()]); export const AuthenticationSchemeSchema = z.union([z.literal("Bearer"), z.literal("HMAC-SHA256")]); -export const TargetingOverlaySchema = z.object({ +export const TargetingOverlaySchema = z.record(z.string(), z.unknown()).and(z.object({ geo_country_any_of: z.array(z.string()).nullish(), geo_region_any_of: z.array(z.string()).nullish(), geo_metro_any_of: z.array(z.string()).nullish(), @@ -449,33 +453,49 @@ export const TargetingOverlaySchema = z.object({ axe_include_segment: z.string().nullish(), axe_exclude_segment: z.string().nullish(), frequency_cap: FrequencyCapSchema.nullish() -}); +})); + +export const CreativeAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ + creative_id: z.string(), + name: z.string(), + format_id: FormatID1Schema, + assets: z.record(z.string(), z.unknown()), + inputs: z.array(z.record(z.string(), z.unknown()).and(z.object({ + name: z.string(), + macros: z.record(z.string(), z.string()).nullish(), + context_description: z.string().nullish() + }))).nullish(), + tags: z.array(z.string()).nullish(), + approved: z.boolean().nullish(), + weight: z.number().nullish(), + placement_ids: z.array(z.string()).nullish() +})); -export const JavaScriptAssetSchema = z.object({ +export const JavaScriptAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ content: z.string(), module_type: JavaScriptModuleTypeSchema.nullish() -}); +})); -export const PromotedOfferingsSchema = z.object({ +export const PromotedOfferingsSchema = z.record(z.string(), z.unknown()).and(z.object({ brand_manifest: BrandManifestReferenceSchema, product_selectors: PromotedProductsSchema.nullish(), - offerings: z.array(z.object({ + offerings: z.array(z.record(z.string(), z.unknown()).and(z.object({ name: z.string(), description: z.string().nullish(), assets: z.array(z.record(z.string(), z.unknown())).nullish() - })).nullish(), - asset_selectors: z.object({ + }))).nullish(), + asset_selectors: z.record(z.string(), z.unknown()).and(z.object({ tags: z.array(z.string()).nullish(), asset_types: z.array(z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("vast"), z.literal("daast"), z.literal("text"), z.literal("url"), z.literal("html"), z.literal("css"), z.literal("javascript"), z.literal("webhook")])).nullish(), exclude_tags: z.array(z.string()).nullish() - }).nullish() -}); + })).nullish() +})); -export const URLAssetSchema = z.object({ +export const URLAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string(), url_type: URLAssetTypeSchema.nullish(), description: z.string().nullish() -}); +})); export const CreateMediaBuyErrorSchema = z.object({ errors: z.array(ErrorSchema), @@ -483,7 +503,7 @@ export const CreateMediaBuyErrorSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const PackageSchema = z.object({ +export const PackageSchema = z.record(z.string(), z.unknown()).and(z.object({ package_id: z.string(), buyer_ref: z.string().nullish(), product_id: z.string().nullish(), @@ -497,23 +517,7 @@ export const PackageSchema = z.object({ format_ids_to_provide: z.array(FormatIDSchema).nullish(), paused: z.boolean().nullish(), ext: ExtensionObjectSchema.nullish() -}); - -export const CreativeAssetSchema = z.object({ - creative_id: z.string(), - name: z.string(), - format_id: FormatID1Schema, - assets: z.record(z.string(), z.union([ImageAssetSchema, VideoAssetSchema, AudioAssetSchema, TextAssetSchema, HTMLAssetSchema, CSSAssetSchema, JavaScriptAssetSchema, VASTAssetSchema, DAASTAssetSchema, PromotedOfferingsSchema, URLAssetSchema])), - inputs: z.array(z.object({ - name: z.string(), - macros: z.record(z.string(), z.string()).nullish(), - context_description: z.string().nullish() - })).nullish(), - tags: z.array(z.string()).nullish(), - approved: z.boolean().nullish(), - weight: z.number().nullish(), - placement_ids: z.array(z.string()).nullish() -}); +})); export const ValidationModeSchema = z.union([z.literal("strict"), z.literal("lenient")]); @@ -536,17 +540,17 @@ export const CreativeStatusSchema = z.union([z.literal("processing"), z.literal( export const CreativeStatus1Schema = z.union([z.literal("processing"), z.literal("approved"), z.literal("rejected"), z.literal("pending_review")]); -export const SubAssetSchema = z.union([z.object({ +export const SubAssetSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ asset_kind: z.literal("media"), asset_type: z.string(), asset_id: z.string(), content_uri: z.string() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ asset_kind: z.literal("text"), asset_type: z.string(), asset_id: z.string(), content: z.union([z.string(), z.array(z.string())]) - })]); + }))]); export const ListCreativesResponseSchema = z.object({ query_summary: z.object({ @@ -662,21 +666,21 @@ export const DeliveryMetricsSchema = z.record(z.string(), z.unknown()).and(z.obj q3_views: z.number().nullish(), q4_views: z.number().nullish() }).nullish(), - dooh_metrics: z.object({ + dooh_metrics: z.record(z.string(), z.unknown()).and(z.object({ loop_plays: z.number().nullish(), screens_used: z.number().nullish(), screen_time_seconds: z.number().nullish(), sov_achieved: z.number().nullish(), calculation_notes: z.string().nullish(), - venue_breakdown: z.array(z.object({ + venue_breakdown: z.array(z.record(z.string(), z.unknown()).and(z.object({ venue_id: z.string(), venue_name: z.string().nullish(), venue_type: z.string().nullish(), impressions: z.number(), loop_plays: z.number().nullish(), screens_used: z.number().nullish() - })).nullish() - }).nullish() + }))).nullish() + })).nullish() })); export const PricingModel1Schema = z.union([z.literal("cpm"), z.literal("vcpm"), z.literal("cpc"), z.literal("cpcv"), z.literal("cpv"), z.literal("cpp"), z.literal("flat_rate")]); @@ -721,12 +725,25 @@ export const ProvidePerformanceFeedbackErrorSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); +export const CreativeManifestSchema = z.record(z.string(), z.unknown()).and(z.object({ + format_id: FormatIDSchema, + promoted_offering: z.string().nullish(), + assets: z.record(z.string(), z.unknown()), + ext: ExtensionObjectSchema.nullish() +})); + export const HTTPMethodSchema = z.union([z.literal("GET"), z.literal("POST")]); export const WebhookResponseTypeSchema = z.union([z.literal("html"), z.literal("json"), z.literal("xml"), z.literal("javascript")]); export const WebhookSecurityMethodSchema = z.union([z.literal("hmac_sha256"), z.literal("api_key"), z.literal("none")]); +export const BuildCreativeSuccessSchema = z.object({ + creative_manifest: CreativeManifestSchema, + context: ContextObjectSchema.nullish(), + ext: ExtensionObjectSchema.nullish() +}); + export const BuildCreativeErrorSchema = z.object({ errors: z.array(ErrorSchema), context: ContextObjectSchema.nullish(), @@ -735,11 +752,18 @@ export const BuildCreativeErrorSchema = z.object({ export const PreviewOutputFormatSchema = z.union([z.literal("url"), z.literal("html")]); +export const CreativeManifest1Schema = z.record(z.string(), z.unknown()).and(z.object({ + format_id: FormatID1Schema, + promoted_offering: z.string().nullish(), + assets: z.record(z.string(), z.unknown()), + ext: ExtensionObjectSchema.nullish() +})); + export const PreviewOutputFormat1Schema = z.union([z.literal("url"), z.literal("html")]); export const PreviewOutputFormat2Schema = z.union([z.literal("url"), z.literal("html")]); -export const PreviewRenderSchema = z.union([z.object({ +export const PreviewRenderSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ render_id: z.string(), output_format: z.literal("url"), preview_url: z.string(), @@ -754,7 +778,7 @@ export const PreviewRenderSchema = z.union([z.object({ supports_fullscreen: z.boolean().nullish(), csp_policy: z.string().nullish() }).nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ render_id: z.string(), output_format: z.literal("html"), preview_html: z.string(), @@ -769,7 +793,7 @@ export const PreviewRenderSchema = z.union([z.object({ supports_fullscreen: z.boolean().nullish(), csp_policy: z.string().nullish() }).nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ render_id: z.string(), output_format: z.literal("both"), preview_url: z.string(), @@ -785,7 +809,7 @@ export const PreviewRenderSchema = z.union([z.object({ supports_fullscreen: z.boolean().nullish(), csp_policy: z.string().nullish() }).nullish() - })]); + }))]); export const PreviewBatchResultSuccessSchema = z.object({ success: z.literal(true).nullish() @@ -795,37 +819,37 @@ export const PreviewBatchResultErrorSchema = z.object({ success: z.literal(false).nullish() }); -export const DestinationSchema = z.union([z.object({ +export const DestinationSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("platform"), platform: z.string(), account: z.string().nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("agent"), agent_url: z.string(), account: z.string().nullish() - })]); + }))]); -export const ActivationKeySchema = z.union([z.object({ +export const ActivationKeySchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("segment_id"), segment_id: z.string() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("key_value"), key: z.string(), value: z.string() - })]); + }))]); -export const ActivationKey1Schema = z.union([z.object({ +export const ActivationKey1Schema = z.union([z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("segment_id"), segment_id: z.string() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("key_value"), key: z.string(), value: z.string() - })]); + }))]); export const SignalCatalogTypeSchema = z.union([z.literal("marketplace"), z.literal("custom"), z.literal("owned")]); -export const DeploymentSchema = z.union([z.object({ +export const DeploymentSchema = z.union([z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("platform"), platform: z.string(), account: z.string().nullish(), @@ -833,7 +857,7 @@ export const DeploymentSchema = z.union([z.object({ activation_key: ActivationKeySchema.nullish(), estimated_activation_duration_minutes: z.number().nullish(), deployed_at: z.string().nullish() - }), z.object({ + })), z.record(z.string(), z.unknown()).and(z.object({ type: z.literal("agent"), agent_url: z.string(), account: z.string().nullish(), @@ -841,7 +865,7 @@ export const DeploymentSchema = z.union([z.object({ activation_key: ActivationKey1Schema.nullish(), estimated_activation_duration_minutes: z.number().nullish(), deployed_at: z.string().nullish() - })]); + }))]); export const ActivateSignalRequestSchema = z.object({ signal_agent_segment_id: z.string(), @@ -875,7 +899,7 @@ export const MediaBuySchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const ProductSchema = z.object({ +export const ProductSchema = z.record(z.string(), z.unknown()).and(z.object({ product_id: z.string(), name: z.string(), description: z.string(), @@ -895,16 +919,16 @@ export const ProductSchema = z.object({ is_custom: z.boolean().nullish(), brief_relevance: z.string().nullish(), expires_at: z.string().nullish(), - product_card: z.object({ + product_card: z.record(z.string(), z.unknown()).and(z.object({ format_id: FormatID1Schema, manifest: z.record(z.string(), z.unknown()) - }).nullish(), - product_card_detailed: z.object({ + })).nullish(), + product_card_detailed: z.record(z.string(), z.unknown()).and(z.object({ format_id: FormatID2Schema, manifest: z.record(z.string(), z.unknown()) - }).nullish(), + })).nullish(), ext: ExtensionObjectSchema.nullish() -}); +})); export const PropertySchema = z.object({ property_id: PropertyIDSchema.nullish(), @@ -936,7 +960,7 @@ export const GetProductsResponseSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const ProductFiltersSchema = z.object({ +export const ProductFiltersSchema = z.record(z.string(), z.unknown()).and(z.object({ delivery_type: DeliveryTypeSchema.nullish(), is_fixed_price: z.boolean().nullish(), format_types: z.array(FormatCategorySchema).nullish(), @@ -948,7 +972,7 @@ export const ProductFiltersSchema = z.object({ budget_range: z.record(z.string(), z.unknown()).nullish(), countries: z.array(z.string()).nullish(), channels: z.array(AdvertisingChannelsSchema).nullish() -}); +})); export const ListCreativeFormatsRequestSchema = z.object({ format_ids: z.array(FormatIDSchema).nullish(), @@ -964,7 +988,7 @@ export const ListCreativeFormatsRequestSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const FormatSchema = z.object({ +export const FormatSchema = z.record(z.string(), z.unknown()).and(z.object({ format_id: FormatIDSchema, name: z.string(), description: z.string().nullish(), @@ -997,20 +1021,41 @@ export const FormatSchema = z.object({ requirements: z.record(z.string(), z.unknown()).nullish() })) })])).nullish(), + assets: z.array(z.union([z.object({ + item_type: z.literal("individual"), + asset_id: z.string(), + asset_type: AssetContentType2Schema, + asset_role: z.string().nullish(), + required: z.boolean(), + requirements: z.record(z.string(), z.unknown()).nullish() + }), z.object({ + item_type: z.literal("repeatable_group"), + asset_group_id: z.string(), + required: z.boolean(), + min_count: z.number(), + max_count: z.number(), + assets: z.array(z.object({ + asset_id: z.string(), + asset_type: AssetContentType3Schema, + asset_role: z.string().nullish(), + required: z.boolean(), + requirements: z.record(z.string(), z.unknown()).nullish() + })) + })])).nullish(), delivery: z.record(z.string(), z.unknown()).nullish(), supported_macros: z.array(z.string()).nullish(), output_format_ids: z.array(FormatID1Schema).nullish(), - format_card: z.object({ + format_card: z.record(z.string(), z.unknown()).and(z.object({ format_id: FormatID2Schema, manifest: z.record(z.string(), z.unknown()) - }).nullish(), - format_card_detailed: z.object({ + })).nullish(), + format_card_detailed: z.record(z.string(), z.unknown()).and(z.object({ format_id: FormatID3Schema, manifest: z.record(z.string(), z.unknown()) - }).nullish() -}); + })).nullish() +})); -export const PackageRequestSchema = z.object({ +export const PackageRequestSchema = z.record(z.string(), z.unknown()).and(z.object({ buyer_ref: z.string(), product_id: z.string(), format_ids: z.array(FormatIDSchema).nullish(), @@ -1022,7 +1067,7 @@ export const PackageRequestSchema = z.object({ creative_ids: z.array(z.string()).nullish(), creatives: z.array(CreativeAssetSchema).nullish(), ext: ExtensionObjectSchema.nullish() -}); +})); export const CreateMediaBuyResponseSchema = z.union([CreateMediaBuySuccessSchema, CreateMediaBuyErrorSchema]); @@ -1038,7 +1083,7 @@ export const SyncCreativesRequestSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const CreativeFiltersSchema = z.object({ +export const CreativeFiltersSchema = z.record(z.string(), z.unknown()).and(z.object({ format: z.string().nullish(), formats: z.array(z.string()).nullish(), status: CreativeStatusSchema.nullish(), @@ -1057,7 +1102,7 @@ export const CreativeFiltersSchema = z.object({ buyer_refs: z.array(z.string()).nullish(), unassigned: z.boolean().nullish(), has_performance_data: z.boolean().nullish() -}); +})); export const UpdateMediaBuyRequestSchema = UpdateMediaBuyRequest1Schema; @@ -1126,7 +1171,15 @@ export const ProvidePerformanceFeedbackRequestSchema = ProvidePerformanceFeedbac export const ProvidePerformanceFeedbackResponseSchema = z.union([ProvidePerformanceFeedbackSuccessSchema, ProvidePerformanceFeedbackErrorSchema]); -export const WebhookAssetSchema = z.object({ +export const BuildCreativeRequestSchema = z.object({ + message: z.string().nullish(), + creative_manifest: CreativeManifestSchema.nullish(), + target_format_id: FormatID1Schema, + context: ContextObjectSchema.nullish(), + ext: ExtensionObjectSchema.nullish() +}); + +export const WebhookAssetSchema = z.record(z.string(), z.unknown()).and(z.object({ url: z.string(), method: HTTPMethodSchema.nullish(), timeout_ms: z.number().nullish(), @@ -1138,21 +1191,40 @@ export const WebhookAssetSchema = z.object({ hmac_header: z.string().nullish(), api_key_header: z.string().nullish() }) -}); +})); -export const CreativeManifestSchema = z.object({ - format_id: FormatIDSchema, - promoted_offering: z.string().nullish(), - assets: z.record(z.string(), z.union([ImageAssetSchema, VideoAssetSchema, AudioAssetSchema, VASTAssetSchema, TextAssetSchema, URLAssetSchema, HTMLAssetSchema, JavaScriptAssetSchema, WebhookAssetSchema, CSSAssetSchema, DAASTAssetSchema, PromotedOfferingsSchema])), - ext: ExtensionObjectSchema.nullish() -}); +export const BuildCreativeResponseSchema = z.union([BuildCreativeSuccessSchema, BuildCreativeErrorSchema]); -export const CreativeManifest1Schema = z.object({ - format_id: FormatID1Schema, - promoted_offering: z.string().nullish(), - assets: z.record(z.string(), z.union([ImageAssetSchema, VideoAssetSchema, AudioAssetSchema, VASTAssetSchema, TextAssetSchema, URLAssetSchema, HTMLAssetSchema, JavaScriptAssetSchema, WebhookAssetSchema, CSSAssetSchema, DAASTAssetSchema, PromotedOfferingsSchema])), - ext: ExtensionObjectSchema.nullish() -}); +export const PreviewCreativeRequestSchema = z.union([z.object({ + request_type: z.literal("single"), + format_id: FormatIDSchema, + creative_manifest: CreativeManifestSchema, + inputs: z.array(z.object({ + name: z.string(), + macros: z.record(z.string(), z.string()).nullish(), + context_description: z.string().nullish() + })).nullish(), + template_id: z.string().nullish(), + output_format: PreviewOutputFormatSchema.nullish(), + context: ContextObjectSchema.nullish(), + ext: ExtensionObjectSchema.nullish() + }), z.object({ + request_type: z.literal("batch"), + requests: z.array(z.object({ + format_id: FormatID2Schema, + creative_manifest: CreativeManifest1Schema, + inputs: z.array(z.object({ + name: z.string(), + macros: z.record(z.string(), z.string()).nullish(), + context_description: z.string().nullish() + })).nullish(), + template_id: z.string().nullish(), + output_format: PreviewOutputFormat1Schema.nullish() + })), + output_format: PreviewOutputFormat2Schema.nullish(), + context: ContextObjectSchema.nullish(), + ext: ExtensionObjectSchema.nullish() + })]); export const PreviewCreativeSingleResponseSchema = z.object({ response_type: z.literal("single"), @@ -1178,12 +1250,12 @@ export const PreviewCreativeBatchResponseSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const SignalFiltersSchema = z.object({ +export const SignalFiltersSchema = z.record(z.string(), z.unknown()).and(z.object({ catalog_types: z.array(SignalCatalogTypeSchema).nullish(), data_providers: z.array(z.string()).nullish(), max_cpm: z.number().nullish(), min_coverage_percentage: z.number().nullish() -}); +})); export const GetSignalsResponseSchema = z.object({ signals: z.array(z.object({ @@ -1265,51 +1337,6 @@ export const ListCreativesRequestSchema = z.object({ ext: ExtensionObjectSchema.nullish() }); -export const BuildCreativeRequestSchema = z.object({ - message: z.string().nullish(), - creative_manifest: CreativeManifestSchema.nullish(), - target_format_id: FormatID1Schema, - context: ContextObjectSchema.nullish(), - ext: ExtensionObjectSchema.nullish() -}); - -export const BuildCreativeSuccessSchema = z.object({ - creative_manifest: CreativeManifestSchema, - context: ContextObjectSchema.nullish(), - ext: ExtensionObjectSchema.nullish() -}); - -export const PreviewCreativeRequestSchema = z.union([z.object({ - request_type: z.literal("single"), - format_id: FormatIDSchema, - creative_manifest: CreativeManifestSchema, - inputs: z.array(z.object({ - name: z.string(), - macros: z.record(z.string(), z.string()).nullish(), - context_description: z.string().nullish() - })).nullish(), - template_id: z.string().nullish(), - output_format: PreviewOutputFormatSchema.nullish(), - context: ContextObjectSchema.nullish(), - ext: ExtensionObjectSchema.nullish() - }), z.object({ - request_type: z.literal("batch"), - requests: z.array(z.object({ - format_id: FormatID2Schema, - creative_manifest: CreativeManifest1Schema, - inputs: z.array(z.object({ - name: z.string(), - macros: z.record(z.string(), z.string()).nullish(), - context_description: z.string().nullish() - })).nullish(), - template_id: z.string().nullish(), - output_format: PreviewOutputFormat1Schema.nullish() - })), - output_format: PreviewOutputFormat2Schema.nullish(), - context: ContextObjectSchema.nullish(), - ext: ExtensionObjectSchema.nullish() - })]); - export const PreviewCreativeResponseSchema = z.union([PreviewCreativeSingleResponseSchema, PreviewCreativeBatchResponseSchema]); export const GetSignalsRequestSchema = z.object({ @@ -1323,5 +1350,3 @@ export const GetSignalsRequestSchema = z.object({ context: ContextObjectSchema.nullish(), ext: ExtensionObjectSchema.nullish() }); - -export const BuildCreativeResponseSchema = z.union([BuildCreativeSuccessSchema, BuildCreativeErrorSchema]); diff --git a/src/lib/types/tools.generated.ts b/src/lib/types/tools.generated.ts index 5819e7a7f..a08ec1660 100644 --- a/src/lib/types/tools.generated.ts +++ b/src/lib/types/tools.generated.ts @@ -192,6 +192,7 @@ export interface BrandManifest { metadata?: { [k: string]: unknown; }; + [k: string]: unknown; }[]; /** * Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection. @@ -217,6 +218,7 @@ export interface BrandManifest { * How frequently the product catalog is updated */ update_frequency?: 'realtime' | 'hourly' | 'daily' | 'weekly'; + [k: string]: unknown; }; /** * Legal disclaimers or required text that must appear in creatives @@ -273,6 +275,7 @@ export interface BrandManifest { */ version?: string; }; + [k: string]: unknown; } /** * Structured filters for product discovery @@ -321,6 +324,7 @@ export interface ProductFilters { * Filter by advertising channels (e.g., ['display', 'video', 'dooh']) */ channels?: AdvertisingChannels[]; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -346,6 +350,7 @@ export interface FormatID { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned. @@ -375,6 +380,7 @@ export type PublisherPropertySelector = * Discriminator indicating all properties from this publisher are included */ selection_type: 'all'; + [k: string]: unknown; } | { /** @@ -391,6 +397,7 @@ export type PublisherPropertySelector = * @minItems 1 */ property_ids: [PropertyID, ...PropertyID[]]; + [k: string]: unknown; } | { /** @@ -407,6 +414,7 @@ export type PublisherPropertySelector = * @minItems 1 */ property_tags: [PropertyTag, ...PropertyTag[]]; + [k: string]: unknown; }; /** * Identifier for a publisher property. Must be lowercase alphanumeric with underscores only. @@ -552,6 +560,7 @@ export interface Product { manifest: { [k: string]: unknown; }; + [k: string]: unknown; }; /** * Optional detailed card with carousel and full specifications. Provides rich product presentation similar to media kit pages. @@ -564,8 +573,10 @@ export interface Product { manifest: { [k: string]: unknown; }; + [k: string]: unknown; }; ext?: ExtensionObject; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -589,6 +600,7 @@ export interface Placement { * @minItems 1 */ format_ids?: [FormatID, ...FormatID[]]; + [k: string]: unknown; } /** * Cost Per Mille (cost per 1,000 impressions) with guaranteed fixed rate - common for direct/guaranteed deals @@ -618,6 +630,7 @@ export interface CPMFixedRatePricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Mille (cost per 1,000 impressions) with auction-based pricing - common for programmatic/non-guaranteed inventory @@ -668,6 +681,7 @@ export interface CPMAuctionPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Viewable Cost Per Mille (cost per 1,000 viewable impressions) with guaranteed fixed rate - impressions meeting MRC viewability standard (50% pixels in-view for 1 second for display, 2 seconds for video) @@ -697,6 +711,7 @@ export interface VCPMFixedRatePricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Viewable Cost Per Mille (cost per 1,000 viewable impressions) with auction-based pricing - impressions meeting MRC viewability standard (50% pixels in-view for 1 second for display, 2 seconds for video) @@ -747,6 +762,7 @@ export interface VCPMAuctionPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Click fixed-rate pricing for performance-driven advertising campaigns @@ -776,6 +792,7 @@ export interface CPCPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Completed View (100% video/audio completion) fixed-rate pricing @@ -805,6 +822,7 @@ export interface CPCVPricingOption { * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per View (at publisher-defined threshold) fixed-rate pricing for video/audio @@ -841,12 +859,15 @@ export interface CPVPricingOption { * Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view') */ duration_seconds: number; + [k: string]: unknown; }; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Cost Per Point (Gross Rating Point) fixed-rate pricing for TV and audio campaigns requiring demographic measurement @@ -884,11 +905,13 @@ export interface CPPPricingOption { * Minimum GRPs/TRPs required for this pricing option */ min_points?: number; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Flat rate pricing for DOOH, sponsorships, and time-based campaigns - fixed cost regardless of delivery volume @@ -946,11 +969,13 @@ export interface FlatRatePricingOption { * Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight') */ daypart?: string; + [k: string]: unknown; }; /** * Minimum spend requirement per package using this pricing option, in the specified currency */ min_spend_per_package?: number; + [k: string]: unknown; } /** * Measurement capabilities included with a product @@ -972,6 +997,7 @@ export interface Measurement { * Reporting frequency and format */ reporting: string; + [k: string]: unknown; } /** * Reporting capabilities available for a product @@ -999,6 +1025,7 @@ export interface ReportingCapabilities { * Metrics available in reporting. Impressions and spend are always implicitly included. */ available_metrics: AvailableMetric[]; + [k: string]: unknown; } /** * Creative requirements and restrictions for a product @@ -1010,6 +1037,7 @@ export interface CreativePolicy { * Whether creative templates are provided */ templates_available: boolean; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1035,6 +1063,7 @@ export interface FormatID1 { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1060,6 +1089,7 @@ export interface FormatID2 { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization. @@ -1091,6 +1121,7 @@ export interface Error { details?: { [k: string]: unknown; }; + [k: string]: unknown; } /** * Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned. @@ -1163,6 +1194,40 @@ export type AssetContentType1 = | 'promoted_offerings' | 'url' | 'webhook'; +/** + * Type of asset + */ +export type AssetContentType2 = + | 'image' + | 'video' + | 'audio' + | 'text' + | 'markdown' + | 'html' + | 'css' + | 'javascript' + | 'vast' + | 'daast' + | 'promoted_offerings' + | 'url' + | 'webhook'; +/** + * Type of asset + */ +export type AssetContentType3 = + | 'image' + | 'video' + | 'audio' + | 'text' + | 'markdown' + | 'html' + | 'css' + | 'javascript' + | 'vast' + | 'daast' + | 'promoted_offerings' + | 'url' + | 'webhook'; /** * Capabilities supported by creative agents for format handling */ @@ -1250,7 +1315,8 @@ export interface Format { )[] ]; /** - * Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames). + * @deprecated + * DEPRECATED: Use 'assets' instead. Array of required assets or asset groups for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames). This field is maintained for backward compatibility; new implementations should use 'assets' with the 'required' boolean on each asset. */ assets_required?: ( | { @@ -1321,6 +1387,82 @@ export interface Format { }[]; } )[]; + /** + * Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory. This field replaces the deprecated 'assets_required' and enables full asset discovery for buyers and AI agents. + */ + assets?: ( + | { + /** + * Discriminator indicating this is an individual asset + */ + item_type: 'individual'; + /** + * Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object. + */ + asset_id: string; + asset_type: AssetContentType2; + /** + * Optional descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only. + */ + asset_role?: string; + /** + * Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory. + */ + required: boolean; + /** + * Technical requirements for this asset (dimensions, file size, duration, etc.). For template formats, use parameters_from_format_id: true to indicate asset parameters must match the format_id parameters (width/height/unit and/or duration_ms). + */ + requirements?: { + [k: string]: unknown; + }; + } + | { + /** + * Discriminator indicating this is a repeatable asset group + */ + item_type: 'repeatable_group'; + /** + * Identifier for this asset group (e.g., 'product', 'slide', 'card') + */ + asset_group_id: string; + /** + * Whether this asset group is required. If true, at least min_count repetitions must be provided. + */ + required: boolean; + /** + * Minimum number of repetitions required (if group is required) or allowed (if optional) + */ + min_count: number; + /** + * Maximum number of repetitions allowed + */ + max_count: number; + /** + * Assets within each repetition of this group + */ + assets: { + /** + * Identifier for this asset within the group + */ + asset_id: string; + asset_type: AssetContentType3; + /** + * Optional descriptive label for this asset's purpose. Not used for referencing assets in manifests—use asset_id instead. This field is for human-readable documentation and UI display only. + */ + asset_role?: string; + /** + * Whether this asset is required within each repetition of the group + */ + required: boolean; + /** + * Technical requirements for this asset. For template formats, use parameters_from_format_id: true to indicate asset parameters must match the format_id parameters (width/height/unit and/or duration_ms). + */ + requirements?: { + [k: string]: unknown; + }; + }[]; + } + )[]; /** * Delivery method specifications (e.g., hosted, VAST, third-party tags) */ @@ -1346,6 +1488,7 @@ export interface Format { manifest: { [k: string]: unknown; }; + [k: string]: unknown; }; /** * Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages. @@ -1358,7 +1501,9 @@ export interface Format { manifest: { [k: string]: unknown; }; + [k: string]: unknown; }; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name @@ -1384,6 +1529,7 @@ export interface FormatID3 { * Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters. */ duration_ms?: number; + [k: string]: unknown; } /** * Standard error structure for task-specific errors and warnings @@ -1424,6 +1570,7 @@ export type VASTAsset = * Tracking events supported by this VAST tag */ tracking_events?: VASTTrackingEvent[]; + [k: string]: unknown; } | { /** @@ -1447,6 +1594,7 @@ export type VASTAsset = * Tracking events supported by this VAST tag */ tracking_events?: VASTTrackingEvent[]; + [k: string]: unknown; }; /** * VAST specification version @@ -1502,6 +1650,7 @@ export type DAASTAsset = * Whether companion display ads are included */ companion_ads?: boolean; + [k: string]: unknown; } | { /** @@ -1525,6 +1674,7 @@ export type DAASTAsset = * Whether companion display ads are included */ companion_ads?: boolean; + [k: string]: unknown; }; /** * DAAST specification version @@ -1680,6 +1830,7 @@ export interface PackageRequest { */ creatives?: CreativeAsset[]; ext?: ExtensionObject; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1710,6 +1861,7 @@ export interface TargetingOverlay { */ axe_exclude_segment?: string; frequency_cap?: FrequencyCap; + [k: string]: unknown; } /** * Frequency capping settings for package-level application @@ -1719,6 +1871,7 @@ export interface FrequencyCap { * Minutes to suppress after impression */ suppress_minutes: number; + [k: string]: unknown; } /** * Creative asset for upload to library - supports static assets, generative formats, and third-party snippets @@ -1737,22 +1890,7 @@ export interface CreativeAsset { * Assets required by the format, keyed by asset_role */ assets: { - /** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` "^[a-zA-Z0-9_-]+$". - */ - [k: string]: - | ImageAsset - | VideoAsset - | AudioAsset - | TextAsset - | HTMLAsset - | CSSAsset - | JavaScriptAsset - | VASTAsset - | DAASTAsset - | PromotedOfferings - | URLAsset; + [k: string]: unknown; }; /** * Preview contexts for generative formats - defines what scenarios to generate previews for @@ -1772,6 +1910,7 @@ export interface CreativeAsset { * Natural language description of the context for AI-generated content */ context_description?: string; + [k: string]: unknown; }[]; /** * User-defined tags for organization and searchability @@ -1791,6 +1930,7 @@ export interface CreativeAsset { * @minItems 1 */ placement_ids?: [string, ...string[]]; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -1816,6 +1956,7 @@ export interface ImageAsset { * Alternative text for accessibility */ alt_text?: string; + [k: string]: unknown; } /** * Video asset with URL and specifications @@ -1845,6 +1986,7 @@ export interface VideoAsset { * Video bitrate in kilobits per second */ bitrate_kbps?: number; + [k: string]: unknown; } /** * Audio asset with URL and specifications @@ -1866,6 +2008,7 @@ export interface AudioAsset { * Audio bitrate in kilobits per second */ bitrate_kbps?: number; + [k: string]: unknown; } /** * Text content asset @@ -1879,6 +2022,7 @@ export interface TextAsset { * Language code (e.g., 'en', 'es', 'fr') */ language?: string; + [k: string]: unknown; } /** * HTML content asset @@ -1892,6 +2036,7 @@ export interface HTMLAsset { * HTML version (e.g., 'HTML5') */ version?: string; + [k: string]: unknown; } /** * CSS stylesheet asset @@ -1905,6 +2050,7 @@ export interface CSSAsset { * CSS media query context (e.g., 'screen', 'print') */ media?: string; + [k: string]: unknown; } /** * JavaScript code asset @@ -1915,6 +2061,7 @@ export interface JavaScriptAsset { */ content: string; module_type?: JavaScriptModuleType; + [k: string]: unknown; } /** * Complete offering specification combining brand manifest, product selectors, and asset filters. Provides all context needed for creative generation about what is being promoted. @@ -1940,6 +2087,7 @@ export interface PromotedOfferings { assets?: { [k: string]: unknown; }[]; + [k: string]: unknown; }[]; /** * Selectors to choose specific assets from the brand manifest @@ -1969,7 +2117,9 @@ export interface PromotedOfferings { * Exclude assets with these tags */ exclude_tags?: string[]; + [k: string]: unknown; }; + [k: string]: unknown; } /** * Inline brand manifest object @@ -1991,6 +2141,7 @@ export interface PromotedProducts { * Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20') */ manifest_query?: string; + [k: string]: unknown; } /** * URL reference asset @@ -2005,6 +2156,7 @@ export interface URLAsset { * Description of what this URL points to */ description?: string; + [k: string]: unknown; } /** * Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization. @@ -2085,6 +2237,7 @@ export interface Package { */ paused?: boolean; ext?: ExtensionObject; + [k: string]: unknown; } /** * Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance). @@ -2104,6 +2257,7 @@ export interface CreativeAssignment { * @minItems 1 */ placement_ids?: [string, ...string[]]; + [k: string]: unknown; } /** * Structured format identifier with agent URL and format name. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. @@ -2439,6 +2593,7 @@ export interface CreativeFilters { * Filter creatives that have performance data when true */ has_performance_data?: boolean; + [k: string]: unknown; } /** * Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned. @@ -2466,6 +2621,7 @@ export type SubAsset = * URL for media assets (images, videos, etc.) */ content_uri: string; + [k: string]: unknown; } | { /** @@ -2484,6 +2640,7 @@ export type SubAsset = * Text content for text-based assets like headlines, body text, CTA text, etc. */ content: string | string[]; + [k: string]: unknown; }; /** @@ -3107,7 +3264,9 @@ export interface DeliveryMetrics { * Number of screens used at this venue */ screens_used?: number; + [k: string]: unknown; }[]; + [k: string]: unknown; }; [k: string]: unknown; } @@ -3313,25 +3472,10 @@ export interface CreativeManifest { * IMPORTANT: Creative manifest validation MUST be performed in the context of the format specification. The format defines what type each asset_id should be, which eliminates any validation ambiguity. */ assets: { - /** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` "^[a-z0-9_]+$". - */ - [k: string]: - | ImageAsset - | VideoAsset - | AudioAsset - | VASTAsset - | TextAsset - | URLAsset - | HTMLAsset - | JavaScriptAsset - | WebhookAsset - | CSSAsset - | DAASTAsset - | PromotedOfferings; + [k: string]: unknown; }; ext?: ExtensionObject; + [k: string]: unknown; } /** * Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height/unit in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250, unit: 'px'}). @@ -3369,6 +3513,7 @@ export interface WebhookAsset { */ api_key_header?: string; }; + [k: string]: unknown; } /** * CSS stylesheet asset @@ -3423,7 +3568,7 @@ export type PreviewCreativeRequest = */ name: string; /** - * Macro values to use for this preview. Supports all universal macros from the format's supported_macros list. See docs/media-buy/creatives/universal-macros.md for available macros. + * Macro values to use for this preview. Supports all universal macros from the format's supported_macros list. See docs/creative/universal-macros.md for available macros. */ macros?: { [k: string]: string; @@ -3511,25 +3656,10 @@ export interface CreativeManifest1 { * IMPORTANT: Creative manifest validation MUST be performed in the context of the format specification. The format defines what type each asset_id should be, which eliminates any validation ambiguity. */ assets: { - /** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` "^[a-z0-9_]+$". - */ - [k: string]: - | ImageAsset - | VideoAsset - | AudioAsset - | VASTAsset - | TextAsset - | URLAsset - | HTMLAsset - | JavaScriptAsset - | WebhookAsset - | CSSAsset - | DAASTAsset - | PromotedOfferings; + [k: string]: unknown; }; ext?: ExtensionObject; + [k: string]: unknown; } @@ -3587,6 +3717,7 @@ export type PreviewRender = */ csp_policy?: string; }; + [k: string]: unknown; } | { /** @@ -3633,6 +3764,7 @@ export type PreviewRender = */ csp_policy?: string; }; + [k: string]: unknown; } | { /** @@ -3683,6 +3815,7 @@ export type PreviewRender = */ csp_policy?: string; }; + [k: string]: unknown; }; /** @@ -3777,6 +3910,7 @@ export type Destination = * Optional account identifier on the platform */ account?: string; + [k: string]: unknown; } | { /** @@ -3791,6 +3925,7 @@ export type Destination = * Optional account identifier on the agent */ account?: string; + [k: string]: unknown; }; /** * Types of signal catalogs available for audience targeting @@ -3846,6 +3981,7 @@ export interface SignalFilters { * Minimum coverage requirement */ min_coverage_percentage?: number; + [k: string]: unknown; } /** * Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned. @@ -3882,6 +4018,7 @@ export type Deployment = * Timestamp when activation completed (if is_live=true) */ deployed_at?: string; + [k: string]: unknown; } | { /** @@ -3909,6 +4046,7 @@ export type Deployment = * Timestamp when activation completed (if is_live=true) */ deployed_at?: string; + [k: string]: unknown; }; /** * The key to use for targeting. Only present if is_live=true AND requester has access to this deployment. @@ -3923,6 +4061,7 @@ export type ActivationKey = * The platform-specific segment identifier to use in campaign targeting */ segment_id: string; + [k: string]: unknown; } | { /** @@ -3937,6 +4076,7 @@ export type ActivationKey = * The targeting parameter value */ value: string; + [k: string]: unknown; }; /** * The key to use for targeting. Only present if is_live=true AND requester has access to this deployment. @@ -3951,6 +4091,7 @@ export type ActivationKey1 = * The platform-specific segment identifier to use in campaign targeting */ segment_id: string; + [k: string]: unknown; } | { /** @@ -3965,6 +4106,7 @@ export type ActivationKey1 = * The targeting parameter value */ value: string; + [k: string]: unknown; }; /** diff --git a/src/lib/utils/format-assets.ts b/src/lib/utils/format-assets.ts new file mode 100644 index 000000000..c9c5b71bf --- /dev/null +++ b/src/lib/utils/format-assets.ts @@ -0,0 +1,153 @@ +// Format Asset Utilities +// Provides backward-compatible access to format assets (v2.6 `assets` field replaces deprecated `assets_required`) + +import type { Format } from '../types/tools.generated'; + +// Internal types - derived from Format['assets'] since schema doesn't export standalone types +type FormatAsset = NonNullable[number]; +type IndividualAsset = Extract; +type RepeatableAssetGroup = Extract; + +/** + * Get assets from a Format, preferring new `assets` field, falling back to `assets_required` + * + * This provides backward compatibility during the migration from `assets_required` to `assets`. + * - If `assets` exists and has items, returns it directly + * - If only `assets_required` exists, normalizes it to the new format (sets required: true) + * - Returns empty array if neither field exists (flexible format) + * + * @param format - The Format object from list_creative_formats response + * @returns Array of assets in the new format structure + * + * @example + * ```typescript + * const formats = await agent.listCreativeFormats({}); + * for (const format of formats.formats) { + * const assets = getFormatAssets(format); + * console.log(`${format.name} has ${assets.length} assets`); + * } + * ``` + */ +export function getFormatAssets(format: Format): FormatAsset[] { + // Prefer new `assets` field (v2.6+) + if (format.assets && format.assets.length > 0) { + return format.assets; + } + + // Fall back to deprecated `assets_required` and normalize + if (format.assets_required && format.assets_required.length > 0) { + return normalizeAssetsRequired(format.assets_required); + } + + return []; +} + +/** + * Convert deprecated assets_required to new assets format + * + * All assets in assets_required are required by definition (that's why they were in that array). + * The new `assets` field has an explicit `required: boolean` to allow both required AND optional assets. + * + * @param assetsRequired - The deprecated assets_required array + * @returns Normalized assets array with explicit required: true + */ +export function normalizeAssetsRequired(assetsRequired: NonNullable): FormatAsset[] { + return assetsRequired.map(asset => ({ + ...asset, + required: true, // assets_required only contained required assets + })) as FormatAsset[]; +} + +/** + * Get only required assets from a Format + * + * @param format - The Format object + * @returns Array of required assets only + * + * @example + * ```typescript + * const requiredAssets = getRequiredAssets(format); + * console.log(`Must provide ${requiredAssets.length} assets`); + * ``` + */ +export function getRequiredAssets(format: Format): FormatAsset[] { + return getFormatAssets(format).filter(asset => asset.required); +} + +/** + * Get only optional assets from a Format + * + * Note: When using deprecated `assets_required`, this will always return empty + * since assets_required only contained required assets. + * + * @param format - The Format object + * @returns Array of optional assets only + * + * @example + * ```typescript + * const optionalAssets = getOptionalAssets(format); + * console.log(`Can optionally provide ${optionalAssets.length} additional assets`); + * ``` + */ +export function getOptionalAssets(format: Format): FormatAsset[] { + return getFormatAssets(format).filter(asset => !asset.required); +} + +/** + * Get individual assets (not repeatable groups) from a Format + * + * @param format - The Format object + * @returns Array of individual assets + */ +export function getIndividualAssets(format: Format): IndividualAsset[] { + return getFormatAssets(format).filter((asset): asset is IndividualAsset => asset.item_type === 'individual'); +} + +/** + * Get repeatable asset groups from a Format + * + * @param format - The Format object + * @returns Array of repeatable asset groups + */ +export function getRepeatableGroups(format: Format): RepeatableAssetGroup[] { + return getFormatAssets(format).filter( + (asset): asset is RepeatableAssetGroup => asset.item_type === 'repeatable_group' + ); +} + +/** + * Check if format uses deprecated assets_required field (for migration warnings) + * + * @param format - The Format object + * @returns true if using deprecated field, false if using new field or neither + * + * @example + * ```typescript + * if (usesDeprecatedAssetsField(format)) { + * console.warn(`Format ${format.name} uses deprecated assets_required field`); + * } + * ``` + */ +export function usesDeprecatedAssetsField(format: Format): boolean { + return !format.assets && !!format.assets_required; +} + +/** + * Get the count of assets in a format (for display purposes) + * + * @param format - The Format object + * @returns Number of assets, or 0 if none defined + */ +export function getAssetCount(format: Format): number { + return getFormatAssets(format).length; +} + +/** + * Check if a format has any assets defined + * + * @param format - The Format object + * @returns true if format has assets, false otherwise + */ +export function hasAssets(format: Format): boolean { + return getAssetCount(format) > 0; +} diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 4bb66d7e5..d93fafe03 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -140,3 +140,16 @@ export type { AdCPResponse } from './response-unwrapper'; // Re-export protocol detection utilities export { detectProtocol, detectProtocolWithTimeout } from './protocol-detection'; + +// Re-export format asset utilities (v2.6 backward compatibility) +export { + getFormatAssets, + normalizeAssetsRequired, + getRequiredAssets, + getOptionalAssets, + getIndividualAssets, + getRepeatableGroups, + usesDeprecatedAssetsField, + getAssetCount, + hasAssets, +} from './format-assets'; diff --git a/src/lib/version.ts b/src/lib/version.ts index 64cf867be..a3eb6efc9 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -4,21 +4,21 @@ /** * AdCP client library version */ -export const LIBRARY_VERSION = '3.3.3'; +export const LIBRARY_VERSION = '3.6.0'; /** * AdCP specification version this library is compatible with */ -export const ADCP_VERSION = 'v2.5'; +export const ADCP_VERSION = 'v2.6'; /** * Full version information */ export const VERSION_INFO = { - library: '3.3.3', - adcp: 'v2.5', + library: '3.6.0', + adcp: 'v2.6', compatible: true, - generatedAt: '2025-12-19T12:20:50.394Z', + generatedAt: '2026-01-06T21:14:13.710Z', } as const; /** diff --git a/src/public/creative-testing.html b/src/public/creative-testing.html index f33891d7a..771eaa31b 100644 --- a/src/public/creative-testing.html +++ b/src/public/creative-testing.html @@ -764,6 +764,11 @@

Debug Logs

state.formats = data.data.formats; displayFormatsTable(state.formats); formatsSection.classList.remove('hidden'); + + // Display deprecation warnings if any + if (data.warnings && data.warnings.length > 0) { + displayWarnings(data.warnings); + } } else { const errorMsg = data.error || 'Unknown error'; showError('Failed to load formats: ' + errorMsg); @@ -912,14 +917,10 @@

Debug Logs

${dimensions} ${ - format.assets_required + getFormatAssets(format).length > 0 ? `
Assets - ${ - Array.isArray(format.assets_required) - ? format.assets_required.length - : 'Custom' - } + ${getFormatAssets(format).length}
` : '' } @@ -945,6 +946,26 @@

Debug Logs

container.innerHTML = html; } + /** + * Get assets from a Format, preferring new `assets` field, falling back to `assets_required` + * This provides backward compatibility for v2.5 -> v2.6 migration + */ + function getFormatAssets(format) { + // Prefer new `assets` field (v2.6+) + if (format.assets && format.assets.length > 0) { + return format.assets; + } + // Fall back to deprecated `assets_required` and normalize + if (format.assets_required && format.assets_required.length > 0) { + // assets_required only contained required assets, so set required: true + return format.assets_required.map(asset => ({ + ...asset, + required: true, + })); + } + return []; + } + function getTypeEmoji(type) { const emojiMap = { display: 'šŸ–¼ļø', @@ -1015,7 +1036,7 @@

${format.name}

// Check if format is generative // A format is generative if it has generation_prompt or output_format_ids - const hasGenerationPrompt = format.assets_required?.some(a => a.asset_id === 'generation_prompt'); + const hasGenerationPrompt = getFormatAssets(format).some(a => a.asset_id === 'generation_prompt'); const isGenerative = (format.output_format_ids && format.output_format_ids.length > 0) || hasGenerationPrompt; console.log('Format:', format.name); console.log('Has output_format_ids:', format.output_format_ids); @@ -1037,7 +1058,7 @@

${format.name}

// Show asset inputs const assetsDiv = document.getElementById('assetInputs'); - const assetsRequired = format.assets_required || []; + const assetsRequired = getFormatAssets(format); if (assetsRequired.length === 0) { assetsDiv.innerHTML = '

This format requires no assets.

'; @@ -1107,13 +1128,33 @@

${format.name}

return ``; } - // For images/videos: file upload only + // For images: URL input (with optional file upload toggle) if (assetType === 'image') { - return ``; + return ` +
+ +
+ + +

šŸ’” Tip: Use https://picsum.photos/WIDTH/HEIGHT for test images

+ `; } + // For videos: URL input (with optional file upload toggle) if (assetType === 'video') { - return ``; + return ` +
+ +
+ + + `; } // Default: text input @@ -1142,9 +1183,23 @@

${format.name}

} } + // Toggle between URL and file input for image/video assets + function toggleMediaInput(idx, type) { + const urlInput = document.getElementById(`asset_${idx}`); + const fileInput = document.getElementById(`asset_${idx}_file`); + + if (type === 'url') { + urlInput.style.display = 'block'; + fileInput.style.display = 'none'; + } else { + urlInput.style.display = 'none'; + fileInput.style.display = 'block'; + } + } + // Collect assets from form function collectAssets() { - const assetsRequired = state.selectedFormat.assets_required || []; + const assetsRequired = getFormatAssets(state.selectedFormat); const creativeAssets = {}; for (let i = 0; i < assetsRequired.length; i++) { @@ -1168,6 +1223,24 @@

${format.name}

// URL input creativeAssets[asset.asset_id] = input.value; } + } else if (asset.asset_type === 'image' || asset.asset_type === 'video') { + // Image/Video assets - check if URL or file mode + const fileInput = document.getElementById(`asset_${i}_file`); + + // Check if URL input is visible (URL mode) + if (input.style.display !== 'none' && input.value) { + // URL mode - wrap in {url: "..."} per schema + creativeAssets[asset.asset_id] = { + url: input.value, + }; + } else if (fileInput && fileInput.files && fileInput.files.length > 0) { + // File mode - show placeholder (file upload not yet implemented) + creativeAssets[asset.asset_id] = { + type: asset.asset_type, + filename: fileInput.files[0].name, + note: 'File upload not yet implemented - would need to upload to storage first', + }; + } } else if (input.type === 'file') { if (input.files.length > 0) { // For file uploads, we'd need to handle actual file upload @@ -1391,6 +1464,56 @@

Generated Assets:

`; previewSection.scrollIntoView({ behavior: 'smooth' }); } + // Display deprecation warnings + function displayWarnings(warnings) { + if (!warnings || warnings.length === 0) return; + + const formatsSection = document.getElementById('formatsSection'); + if (!formatsSection) return; + + // Remove any existing warnings + const existingWarnings = formatsSection.querySelector('.deprecation-warnings'); + if (existingWarnings) { + existingWarnings.remove(); + } + + // Create warnings container + const warningsDiv = document.createElement('div'); + warningsDiv.className = 'deprecation-warnings'; + warningsDiv.style.cssText = ` + background: #fff3cd; + border: 1px solid #ffc107; + border-left: 4px solid #ffc107; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1.5rem; + `; + + const warningsHtml = warnings + .map( + warning => ` +
+ āš ļø +
+ Deprecation Warning +

${warning.replace('āš ļø', '').trim()}

+
+
+ ` + ) + .join(''); + + warningsDiv.innerHTML = warningsHtml; + + // Insert at the beginning of the formats section (after the h3) + const h3 = formatsSection.querySelector('h3'); + if (h3 && h3.parentElement) { + h3.parentElement.insertBefore(warningsDiv, h3.nextSibling.nextSibling); + } else { + formatsSection.insertBefore(warningsDiv, formatsSection.firstChild); + } + } + // Show error message function showError(message) { // Show error in preview section if available, otherwise in assets section @@ -1636,7 +1759,7 @@

Generated Assets:

`; } const example = QUICK_EXAMPLES[exampleKey]; - const assetsRequired = state.selectedFormat?.assets_required || []; + const assetsRequired = state.selectedFormat ? getFormatAssets(state.selectedFormat) : []; console.log('Loading example:', exampleKey); console.log( diff --git a/src/public/index.html b/src/public/index.html index fa774cf20..cf6b735f2 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -3215,6996 +3215,7023 @@

Active Tasks

diff --git a/src/server/server.ts b/src/server/server.ts index d2479cea4..a9caa00f1 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -28,6 +28,8 @@ import { isCreateMediaBuyCompleted, isUpdateMediaBuyCompleted, isSyncCreativesCompleted, + // Format utilities + usesDeprecatedAssetsField, } from '../lib'; import type { CreateMediaBuyAsyncInputRequired, @@ -229,7 +231,8 @@ const clientConfig: SingleAgentClientConfig = { // āœ… Use type guard for completed status if (isSyncCreativesCompleted(metadata, response)) { - const creativesCount = 'creatives' in response ? response.creatives?.length || 0 : 0; + const creativesCount = + 'creatives' in response && Array.isArray(response.creatives) ? response.creatives.length : 0; app.log.info(`[${status}] Creatives synced: ${creativesCount} for ${metadata.operation_id}`); return; } @@ -1455,12 +1458,26 @@ app.post<{ const adcpClient = creativeClient.getClient(); const result = await adcpClient.listCreativeFormats(params); + // Check for deprecated assets_required usage + const warnings: string[] = []; + const formats = result.data?.formats || []; + const deprecatedFormats = formats + .filter((format: any) => usesDeprecatedAssetsField(format)) + .map((format: any) => format.format_id?.id || format.format_id || format.name || 'unknown'); + + if (deprecatedFormats.length > 0) { + warnings.push( + `āš ļø DEPRECATION: ${deprecatedFormats.length} format(s) using deprecated 'assets_required' field. Please migrate to use 'assets' instead.` + ); + } + return reply.send({ success: result.success, - data: { formats: result.data?.formats || [] }, + data: { formats }, error: result.error, metadata: result.metadata || {}, debug_logs: result.debug_logs || [], + warnings, timestamp: new Date().toISOString(), }); } catch (error) { diff --git a/test/lib/format-assets.test.js b/test/lib/format-assets.test.js new file mode 100644 index 000000000..46e0bbc72 --- /dev/null +++ b/test/lib/format-assets.test.js @@ -0,0 +1,274 @@ +// Unit tests for format-assets utilities +const { test, describe } = require('node:test'); +const assert = require('node:assert'); + +// Import the format-assets utilities +const { + getFormatAssets, + normalizeAssetsRequired, + getRequiredAssets, + getOptionalAssets, + getIndividualAssets, + getRepeatableGroups, + usesDeprecatedAssetsField, + getAssetCount, + hasAssets, +} = require('../../dist/lib/utils/format-assets.js'); + +// Test fixtures +const createV26Format = (assets = []) => ({ + format_id: { agent_url: 'https://test.agent/', id: 'test_format' }, + name: 'Test Format v2.6', + type: 'display', + assets, +}); + +const createV25Format = (assetsRequired = []) => ({ + format_id: { agent_url: 'https://test.agent/', id: 'test_format' }, + name: 'Test Format v2.5', + type: 'display', + assets_required: assetsRequired, +}); + +const createIndividualAsset = (overrides = {}) => ({ + item_type: 'individual', + asset_id: 'banner_image', + asset_type: 'image', + required: true, + ...overrides, +}); + +const createRepeatableGroup = (overrides = {}) => ({ + item_type: 'repeatable_group', + asset_group_id: 'product_images', + required: true, + min_count: 1, + max_count: 5, + assets: [{ asset_id: 'image', asset_type: 'image', required: true }], + ...overrides, +}); + +describe('Format Assets Utilities', () => { + describe('getFormatAssets', () => { + test('should return assets from v2.6 format', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'hero', required: true }), + createIndividualAsset({ asset_id: 'logo', required: false }), + ]); + + const assets = getFormatAssets(format); + + assert.strictEqual(assets.length, 2); + assert.strictEqual(assets[0].asset_id, 'hero'); + assert.strictEqual(assets[1].asset_id, 'logo'); + }); + + test('should return normalized assets from v2.5 format (assets_required)', () => { + const format = createV25Format([ + { item_type: 'individual', asset_id: 'banner', asset_type: 'image' }, + { item_type: 'individual', asset_id: 'headline', asset_type: 'text' }, + ]); + + const assets = getFormatAssets(format); + + assert.strictEqual(assets.length, 2); + // assets_required items should have required: true after normalization + assert.strictEqual(assets[0].required, true); + assert.strictEqual(assets[1].required, true); + }); + + test('should prefer v2.6 assets over deprecated assets_required', () => { + // Format with both fields (v2.6 takes precedence) + const format = { + format_id: { agent_url: 'https://test.agent/', id: 'test' }, + assets: [createIndividualAsset({ asset_id: 'new_asset', required: false })], + assets_required: [{ item_type: 'individual', asset_id: 'old_asset', asset_type: 'image' }], + }; + + const assets = getFormatAssets(format); + + assert.strictEqual(assets.length, 1); + assert.strictEqual(assets[0].asset_id, 'new_asset'); + assert.strictEqual(assets[0].required, false); + }); + + test('should return empty array for format with no assets', () => { + const format = createV26Format([]); + const assets = getFormatAssets(format); + assert.deepStrictEqual(assets, []); + }); + }); + + describe('normalizeAssetsRequired', () => { + test('should set required: true for all assets', () => { + const assetsRequired = [ + { item_type: 'individual', asset_id: 'img1', asset_type: 'image' }, + { item_type: 'individual', asset_id: 'img2', asset_type: 'image' }, + ]; + + const normalized = normalizeAssetsRequired(assetsRequired); + + assert.strictEqual(normalized[0].required, true); + assert.strictEqual(normalized[1].required, true); + }); + + test('should preserve other fields during normalization', () => { + const assetsRequired = [ + { + item_type: 'individual', + asset_id: 'banner', + asset_type: 'image', + requirements: { min_width: 300 }, + }, + ]; + + const normalized = normalizeAssetsRequired(assetsRequired); + + assert.strictEqual(normalized[0].asset_id, 'banner'); + assert.strictEqual(normalized[0].asset_type, 'image'); + assert.deepStrictEqual(normalized[0].requirements, { min_width: 300 }); + }); + }); + + describe('getRequiredAssets', () => { + test('should return only required assets', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'required1', required: true }), + createIndividualAsset({ asset_id: 'optional1', required: false }), + createIndividualAsset({ asset_id: 'required2', required: true }), + ]); + + const required = getRequiredAssets(format); + + assert.strictEqual(required.length, 2); + assert.ok(required.every(a => a.required === true)); + }); + + test('should return all assets from v2.5 format (all are required)', () => { + const format = createV25Format([ + { item_type: 'individual', asset_id: 'img1', asset_type: 'image' }, + { item_type: 'individual', asset_id: 'img2', asset_type: 'image' }, + ]); + + const required = getRequiredAssets(format); + + assert.strictEqual(required.length, 2); + }); + }); + + describe('getOptionalAssets', () => { + test('should return only optional assets', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'required1', required: true }), + createIndividualAsset({ asset_id: 'optional1', required: false }), + createIndividualAsset({ asset_id: 'optional2', required: false }), + ]); + + const optional = getOptionalAssets(format); + + assert.strictEqual(optional.length, 2); + assert.ok(optional.every(a => a.required === false)); + }); + + test('should return empty array from v2.5 format (all are required)', () => { + const format = createV25Format([{ item_type: 'individual', asset_id: 'img1', asset_type: 'image' }]); + + const optional = getOptionalAssets(format); + + assert.strictEqual(optional.length, 0); + }); + }); + + describe('getIndividualAssets', () => { + test('should return only individual assets', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'individual1' }), + createRepeatableGroup({ asset_group_id: 'group1' }), + createIndividualAsset({ asset_id: 'individual2' }), + ]); + + const individuals = getIndividualAssets(format); + + assert.strictEqual(individuals.length, 2); + assert.ok(individuals.every(a => a.item_type === 'individual')); + }); + }); + + describe('getRepeatableGroups', () => { + test('should return only repeatable groups', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'individual1' }), + createRepeatableGroup({ asset_group_id: 'group1' }), + createRepeatableGroup({ asset_group_id: 'group2' }), + ]); + + const groups = getRepeatableGroups(format); + + assert.strictEqual(groups.length, 2); + assert.ok(groups.every(a => a.item_type === 'repeatable_group')); + }); + }); + + describe('usesDeprecatedAssetsField', () => { + test('should return true for v2.5 format using assets_required', () => { + const format = createV25Format([{ item_type: 'individual', asset_id: 'img', asset_type: 'image' }]); + + assert.strictEqual(usesDeprecatedAssetsField(format), true); + }); + + test('should return false for v2.6 format using assets', () => { + const format = createV26Format([createIndividualAsset({ asset_id: 'img' })]); + + assert.strictEqual(usesDeprecatedAssetsField(format), false); + }); + + test('should return false for format with both fields (v2.6 takes precedence)', () => { + const format = { + format_id: { agent_url: 'https://test.agent/', id: 'test' }, + assets: [createIndividualAsset()], + assets_required: [{ item_type: 'individual', asset_id: 'old', asset_type: 'image' }], + }; + + assert.strictEqual(usesDeprecatedAssetsField(format), false); + }); + + test('should return false for format with no assets', () => { + const format = createV26Format([]); + assert.strictEqual(usesDeprecatedAssetsField(format), false); + }); + }); + + describe('getAssetCount', () => { + test('should count total assets', () => { + const format = createV26Format([ + createIndividualAsset({ asset_id: 'a1' }), + createIndividualAsset({ asset_id: 'a2' }), + createRepeatableGroup({ asset_group_id: 'g1' }), + ]); + + assert.strictEqual(getAssetCount(format), 3); + }); + + test('should return 0 for format with no assets', () => { + const format = createV26Format([]); + assert.strictEqual(getAssetCount(format), 0); + }); + }); + + describe('hasAssets', () => { + test('should return true for format with assets', () => { + const format = createV26Format([createIndividualAsset()]); + assert.strictEqual(hasAssets(format), true); + }); + + test('should return false for format with no assets', () => { + const format = createV26Format([]); + assert.strictEqual(hasAssets(format), false); + }); + + test('should return true for v2.5 format with assets_required', () => { + const format = createV25Format([{ item_type: 'individual', asset_id: 'img', asset_type: 'image' }]); + assert.strictEqual(hasAssets(format), true); + }); + }); +}); diff --git a/test/lib/testing-deprecation.test.js b/test/lib/testing-deprecation.test.js new file mode 100644 index 000000000..6693562f5 --- /dev/null +++ b/test/lib/testing-deprecation.test.js @@ -0,0 +1,134 @@ +// Unit tests for testing framework deprecation warnings +const { test, describe, mock } = require('node:test'); +const assert = require('node:assert'); + +describe('Testing Framework Deprecation Warnings', () => { + test('should add warning when format uses assets_required', async () => { + // Mock the ADCPMultiAgentClient + const mockClient = { + agent: () => ({ + getAgentInfo: async () => ({ + name: 'Test Agent', + tools: [{ name: 'list_creative_formats' }], + }), + executeTask: async taskName => { + if (taskName === 'list_creative_formats') { + return { + success: true, + data: { + formats: [ + { + format_id: 'deprecated_format', + name: 'Deprecated Format', + type: 'display', + // Using deprecated assets_required field + assets_required: [{ item_type: 'individual', asset_id: 'image', asset_type: 'image' }], + }, + ], + }, + }; + } + return { success: false, error: 'Unknown task' }; + }, + }), + }; + + // Import utilities we can test directly + const { usesDeprecatedAssetsField, getFormatAssets } = require('../../dist/lib/utils/format-assets.js'); + + // Test the deprecation detection logic + const deprecatedFormat = { + format_id: 'deprecated_format', + name: 'Deprecated Format', + type: 'display', + assets_required: [{ item_type: 'individual', asset_id: 'image', asset_type: 'image' }], + }; + + const v26Format = { + format_id: 'v26_format', + name: 'V2.6 Format', + type: 'display', + assets: [{ item_type: 'individual', asset_id: 'image', asset_type: 'image', required: true }], + }; + + // Verify deprecation detection + assert.strictEqual(usesDeprecatedAssetsField(deprecatedFormat), true, 'Should detect deprecated format'); + assert.strictEqual(usesDeprecatedAssetsField(v26Format), false, 'Should not flag v2.6 format'); + + // Verify getFormatAssets normalizes deprecated format + const normalizedAssets = getFormatAssets(deprecatedFormat); + assert.strictEqual(normalizedAssets.length, 1, 'Should have one asset'); + assert.strictEqual(normalizedAssets[0].required, true, 'Normalized asset should have required=true'); + }); + + test('should not add warning when format uses v2.6 assets field', async () => { + const { usesDeprecatedAssetsField } = require('../../dist/lib/utils/format-assets.js'); + + const v26Format = { + format_id: 'modern_format', + name: 'Modern Format', + type: 'display', + assets: [ + { item_type: 'individual', asset_id: 'required_image', asset_type: 'image', required: true }, + { item_type: 'individual', asset_id: 'optional_image', asset_type: 'image', required: false }, + ], + }; + + assert.strictEqual(usesDeprecatedAssetsField(v26Format), false, 'Should not flag v2.6 format'); + }); + + test('should count deprecation warnings in summary', () => { + const { formatTestResultsSummary } = require('../../dist/lib/testing/formatter.js'); + + const resultWithWarnings = { + agent_url: 'https://test.agent/', + scenario: 'creative_flow', + overall_passed: true, + total_duration_ms: 1000, + dry_run: true, + steps: [ + { step: 'Step 1', passed: true, duration_ms: 100 }, + { + step: 'Discover formats', + passed: true, + duration_ms: 200, + warnings: ['āš ļø DEPRECATION: 2 format(s) use deprecated assets_required field'], + }, + { step: 'Step 3', passed: true, duration_ms: 100 }, + ], + }; + + const summary = formatTestResultsSummary(resultWithWarnings); + assert.ok(summary.includes('āš ļø'), 'Summary should include warning indicator'); + assert.ok(summary.includes('warning'), 'Summary should mention warnings'); + }); + + test('should include warnings in formatted output', () => { + const { formatTestResults } = require('../../dist/lib/testing/formatter.js'); + + const resultWithWarnings = { + agent_url: 'https://test.agent/', + scenario: 'creative_flow', + overall_passed: true, + total_duration_ms: 1000, + dry_run: true, + summary: '3/3 passed', + steps: [ + { + step: 'Discover formats', + task: 'list_creative_formats', + passed: true, + duration_ms: 200, + details: 'Found 2 format(s)', + warnings: [ + "āš ļø DEPRECATION: 2 format(s) use 'assets_required' field which is deprecated and will be removed in a future version.", + ], + }, + ], + }; + + const output = formatTestResults(resultWithWarnings); + assert.ok(output.includes('DEPRECATION'), 'Output should include deprecation warning'); + assert.ok(output.includes('assets_required'), 'Output should mention assets_required'); + }); +}); diff --git a/test/type-generator-strictness.test.js b/test/type-generator-strictness.test.js index 7353e3897..63d77a976 100644 --- a/test/type-generator-strictness.test.js +++ b/test/type-generator-strictness.test.js @@ -52,7 +52,14 @@ test('generated types maintain strict schema enforcement', () => { // * Asset manifests (format-defined structures) // * Error details (task-specific information) // * BrandManifest intersections (inherited from allOf pattern) - const MAX_ALLOWED = 29; + // + // Updated from 29 to 105 for AdCP v2.6.0 schema changes: + // - Upstream schema added additionalProperties: true to many core types for extensibility + // - Affected types include: Package, TargetingOverlay, FrequencyCap, FormatID, CreativeAssignment, + // ImageAsset, VideoAsset, AudioAsset, VASTAsset, DAASTAsset, BrandManifest, ProductFilters, + // PublisherPropertySelector variants, and many others + // - This is intentional for forward compatibility - allows agents to include custom fields + const MAX_ALLOWED = 105; console.log(`šŸ“Š Type strictness metrics:`); console.log(` Index signatures found: ${count}`); @@ -95,7 +102,9 @@ test('core types maintain strict schema enforcement', () => { const indexSignatures = coreContent.match(/\[k: string\]: unknown/g) || []; const count = indexSignatures.length; - const MAX_CORE_ALLOWED = 15; + // Updated from 15 to 75 for AdCP v2.6.0 schema changes: + // - Upstream schema added additionalProperties: true to core types for extensibility + const MAX_CORE_ALLOWED = 75; console.log(`šŸ“Š Core types strictness:`); console.log(` Index signatures found: ${count}`);