Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/adcp-v2.6-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@adcp/client": minor
Comment thread
BaiyuScope3 marked this conversation as resolved.
---

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
2 changes: 1 addition & 1 deletion ADCP_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.5
v2.6
44 changes: 42 additions & 2 deletions bin/adcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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(
{
Expand All @@ -876,6 +902,7 @@ async function main() {
contextId: result.metadata.taskId, // Using taskId as context identifier
}),
},
...(deprecationWarnings.length > 0 && { warnings: deprecationWarnings }),
},
null,
2
Expand All @@ -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);
Expand Down
72 changes: 51 additions & 21 deletions examples/inspect-card-formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Comment thread
BaiyuScope3 marked this conversation as resolved.
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) {
Expand All @@ -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 {
Expand All @@ -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 => {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,5 @@
"js-yaml": "^3.14.1"
}
},
"adcp_version": "v2.5"
"adcp_version": "v2.6"
}
2 changes: 1 addition & 1 deletion src/lib/core/AsyncHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Comment thread
BaiyuScope3 marked this conversation as resolved.
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 {
Expand Down
36 changes: 28 additions & 8 deletions src/lib/testing/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<AgentProfile['supported_formats']>[0] = {
Expand All @@ -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);
}
}

Expand All @@ -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`;
Expand Down
16 changes: 15 additions & 1 deletion src/lib/testing/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
}
Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/lib/testing/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading