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
4 changes: 4 additions & 0 deletions .changeset/github-issue-offer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add GitHub issue offer for open-source agent test failures
2 changes: 1 addition & 1 deletion .github/workflows/check-testable-snippets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

if [ -s changed_files.txt ]; then
echo "📋 Checking documentation changes for testable snippets..."
node scripts/check-testable-snippets.js
node scripts/check-testable-snippets.cjs
else
echo "✓ No documentation files changed"
fi
85 changes: 84 additions & 1 deletion docs/media-buy/advanced-topics/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,90 @@ AdCP provides a **public test agent** with free credentials for development and

## Protocol Compliance Testing

Use the [AdCP Protocol Test Harness](https://testing.adcontextprotocol.org) to validate your implementation's compliance with the AdCP specification. This interactive tool allows you to test all AdCP tasks and verify correct behavior across different scenarios.
### Testing via Addie

The easiest way to test your AdCP agent is to ask Addie in Slack:

> "Hey Addie, test my sales agent at https://sales.example.com"

Addie can run comprehensive E2E tests including:

**Standard Scenarios:**
- **health_check** - Verify agent responds
- **discovery** - Test `get_products`, `list_creative_formats`, `list_authorized_properties`
- **create_media_buy** - Discovery + create a test campaign
- **full_sales_flow** - Complete lifecycle: create → update → delivery
- **creative_sync** - Test `sync_creatives` flow
- **creative_inline** - Test inline creatives in `create_media_buy`
- **pricing_models** - Analyze pricing options across channels

**Edge Case Scenarios:**
- **error_handling** - Verify proper discriminated union error responses
- **validation** - Test rejection of invalid inputs (negative budgets, invalid enums)
- **pricing_edge_cases** - Test auction vs fixed pricing, min_spend requirements, bid_price handling
- **temporal_validation** - Test date/time ordering, ISO 8601 format validation

**Behavioral Analysis:**
- **behavior_analysis** - Analyze agent characteristics: authentication requirements, brand_manifest requirements, brief relevance filtering, channel filtering behavior

By default tests run in dry-run mode. For real testing, ask Addie to run without dry-run.

### Sales Agent Compliance Checklist

Use this checklist to verify your sales agent implementation covers all required features:

**Core Discovery (Required)**
- [ ] `get_products` - Returns products with pricing_options, format_ids, delivery_type
- [ ] `list_creative_formats` - Returns supported formats and creative agents
- [ ] `list_authorized_properties` - Returns publisher domains (if applicable)

**Media Buy Lifecycle (Required)**
- [ ] `create_media_buy` - Accepts packages with product_id, pricing_option_id, budget
- [ ] `update_media_buy` - Supports PATCH semantics for budget, pacing, targeting
- [ ] `get_media_buy_delivery` - Returns impressions, spend, status

**Creative Management (Required for most channels)**
- [ ] `sync_creatives` - Upsert creatives with per-item action tracking
- [ ] `list_creatives` - Query creative library with filtering
- [ ] Support inline creatives in `create_media_buy`
- [ ] Support creative references (`creative_ids`)

**Pricing Models (as applicable)**
- [ ] CPM - Cost per thousand impressions
- [ ] vCPM - Viewable CPM (MRC standard)
- [ ] CPCV - Cost per completed view
- [ ] CPC - Cost per click
- [ ] CPP - Cost per rating point (TV/radio)
- [ ] Flat rate - Fixed cost sponsorships
- [ ] Auction pricing - Support bid_price when is_fixed=false

**Creative Types**
- [ ] Static creatives (image, video assets)
- [ ] Reference creatives (creative_ids to existing library)
- [ ] Generative creatives (manifest-based)
- [ ] Parameterized creatives (with substitution)

**Response Patterns**
- [ ] Discriminated union responses (success XOR errors)
- [ ] Schema-compliant responses (validate against JSON schemas)
- [ ] Async operations return status: submitted/working/completed
- [ ] Per-item errors in batch operations (e.g., sync_creatives)

**Testing Support**
- [ ] `X-Dry-Run` header support
- [ ] `X-Test-Session-ID` for parallel test isolation
- [ ] `X-Mock-Time` for time simulation

**Edge Case Validation (Required)**
- [ ] Reject negative budget values
- [ ] Reject invalid pacing enum values
- [ ] Reject end_time before start_time
- [ ] Reject invalid ISO 8601 date formats
- [ ] Return proper error for non-existent product_id
- [ ] Require bid_price for auction pricing options
- [ ] Reject budget below min_spend_per_package
- [ ] Reject creative weight > 100
- [ ] Return discriminated union error responses (success XOR errors, never both)

## Testing Modes

Expand Down
8 changes: 4 additions & 4 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 @@ -37,7 +37,7 @@
"verify-version-sync": "node scripts/verify-version-sync.cjs"
},
"dependencies": {
"@adcp/client": "^3.4.0",
"@adcp/client": "^3.5.0",
"@anthropic-ai/sdk": "^0.71.2",
"@modelcontextprotocol/sdk": "^1.24.3",
"@mozilla/readability": "^0.6.0",
Expand Down
114 changes: 112 additions & 2 deletions server/public/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,55 @@
color: white;
}

/* Image support */
.message-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 8px 0;
display: block;
}

.message-content img:hover {
cursor: pointer;
}

/* iframe support for creative previews */
.message-content .creative-preview-container {
position: relative;
width: 100%;
margin: 12px 0;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--color-border);
background: var(--color-bg-subtle);
}

.message-content .creative-preview-container iframe {
width: 100%;
min-height: 200px;
border: none;
display: block;
}

.message-content .creative-preview-label {
font-size: 11px;
color: var(--color-text-muted);
padding: 4px 8px;
background: var(--color-bg-card);
border-top: 1px solid var(--color-border);
}

/* Inline HTML creative container */
.message-content .creative-html-container {
margin: 12px 0;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--color-border);
background: white;
padding: 16px;
}

.message-content ul, .message-content ol {
margin: 8px 0;
padding-left: 20px;
Expand Down Expand Up @@ -623,8 +672,8 @@ <h2>Hi! I'm Addie</h2>
<div class="suggested-prompts" id="suggestedPrompts">
<button class="suggested-prompt" data-prompt="What is AdCP?">What is AdCP?</button>
<button class="suggested-prompt" data-prompt="How do I get started with AdCP?">How do I get started?</button>
<button class="suggested-prompt" data-prompt="Try AdCP with a test agent">Try AdCP live</button>
<button class="suggested-prompt" data-prompt="What is agentic advertising?">What is agentic advertising?</button>
<button class="suggested-prompt" data-prompt="How do I become an AAO member?">Become a member</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -856,7 +905,7 @@ <h2>Hi! I'm Addie</h2>
gfm: true, // GitHub flavored markdown
});

// Use marked's built-in renderer with custom link handling
// Use marked's built-in renderer with custom link and image handling
const renderer = new marked.Renderer();
renderer.link = function(href, title, text) {
// Handle marked v17+ which passes an object
Expand All @@ -870,9 +919,46 @@ <h2>Hi! I'm Addie</h2>
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
};

// Custom image renderer
renderer.image = function(href, title, text) {
// Handle marked v17+ which passes an object
if (typeof href === 'object') {
const img = href;
href = img.href;
title = img.title;
text = img.text;
}
const titleAttr = title ? ` title="${title}"` : '';
const altAttr = text ? ` alt="${text}"` : ' alt="Image"';
// Make images clickable to open in new tab
return `<a href="${href}" target="_blank" rel="noopener noreferrer"><img src="${href}"${altAttr}${titleAttr} loading="lazy"></a>`;
};

return marked.parse(text, { renderer });
}

// Render creative preview (iframe or HTML)
function renderCreativePreview(previewUrl, label) {
if (!previewUrl) return '';
const safeLabel = label ? label.replace(/</g, '&lt;').replace(/>/g, '&gt;') : 'Creative Preview';
return `
<div class="creative-preview-container">
<iframe src="${previewUrl}" sandbox="allow-scripts allow-same-origin" loading="lazy"></iframe>
<div class="creative-preview-label">${safeLabel}</div>
</div>
`;
}

// Render inline HTML creative
function renderCreativeHtml(html, label) {
if (!html) return '';
return `
<div class="creative-html-container">
${html}
</div>
`;
}

// Add message to chat
function addMessage(content, role, messageId = null) {
// Hide welcome message
Expand Down Expand Up @@ -1291,9 +1377,33 @@ <h2>Hi! I'm Addie</h2>
}
});

// Check for prompt in query string (e.g., ?prompt=Try%20AdCP)
function checkQueryPrompt() {
const params = new URLSearchParams(window.location.search);
const prompt = params.get('prompt');
if (prompt && prompt.trim()) {
// Wait for Addie to be ready before sending
const waitForReady = setInterval(() => {
if (isReady) {
clearInterval(waitForReady);
chatInput.value = prompt.trim();
autoResize();
updateSendButton();
sendMessage();
// Clean URL without reloading
window.history.replaceState({}, '', window.location.pathname);
}
}, 100);
// Timeout after 10 seconds
setTimeout(() => clearInterval(waitForReady), 10000);
}
}

// Initialize
checkStatus();
checkImpersonation();
// Check for prompt in URL after status check
setTimeout(checkQueryPrompt, 500);
// Check status periodically
setInterval(checkStatus, 30000);

Expand Down
Loading