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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@ uv.lock
IMPLEMENTATION_PLAN.md
IMPLEMENTATION_SUMMARY.md
TESTING_STATUS.md
IMPLEMENTATION_PLAN.md
204 changes: 204 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# ADCP Python SDK - Agent Reference

## Server Handler Methods

Override these in your `ADCPHandler` subclass. Unimplemented methods return `not_supported()`.

| Method | Domain | Request Type | Description |
|---|---|---|---|
| `get_adcp_capabilities` | protocol | GetAdcpCapabilitiesRequest | Declare supported domains/features |
| `get_products` | media_buy | GetProductsRequest | Return ad products matching a brief |
| `list_creative_formats` | media_buy | ListCreativeFormatsRequest | List available creative formats |
| `create_media_buy` | media_buy | CreateMediaBuyRequest | Create a new media buy |
| `update_media_buy` | media_buy | UpdateMediaBuyRequest | Update an existing media buy |
| `get_media_buys` | media_buy | GetMediaBuysRequest | List media buys |
| `get_media_buy_delivery` | media_buy | GetMediaBuyDeliveryRequest | Get delivery metrics |
| `provide_performance_feedback` | media_buy | ProvidePerformanceFeedbackRequest | Send conversion data |
| `sync_creatives` | media_buy | SyncCreativesRequest | Sync creative assets |
| `list_creatives` | media_buy | ListCreativesRequest | List synced creatives |
| `build_creative` | creative | BuildCreativeRequest | Generate a creative |
| `preview_creative` | creative | PreviewCreativeRequest | Preview a creative |
| `get_creative_delivery` | creative | GetCreativeDeliveryRequest | Get creative delivery tags |
| `get_creative_features` | governance | GetCreativeFeaturesRequest | Get creative feature definitions |
| `get_signals` | signals | GetSignalsRequest | Discover audience signals |
| `activate_signal` | signals | ActivateSignalRequest | Activate a signal |
| `list_accounts` | media_buy | ListAccountsRequest | List advertiser accounts |
| `sync_accounts` | media_buy | SyncAccountsRequest | Sync account data |
| `get_account_financials` | media_buy | GetAccountFinancialsRequest | Get financial details |
| `report_usage` | media_buy | ReportUsageRequest | Report usage metrics |
| `sync_event_sources` | media_buy | SyncEventSourcesRequest | Register conversion pixels |
| `log_event` | media_buy | LogEventRequest | Log conversion events |
| `sync_audiences` | media_buy | SyncAudiencesRequest | Sync audience segments |
| `sync_catalogs` | media_buy | SyncCatalogsRequest | Sync product catalogs |
| `sync_governance` | account | SyncGovernanceRequest | Sync governance config |
| `create_content_standards` | governance | CreateContentStandardsRequest | Create content standards |
| `get_content_standards` | governance | GetContentStandardsRequest | Get content standards |
| `list_content_standards` | governance | ListContentStandardsRequest | List content standards |
| `update_content_standards` | governance | UpdateContentStandardsRequest | Update content standards |
| `calibrate_content` | governance | CalibrateContentRequest | Evaluate content compliance |
| `validate_content_delivery` | governance | ValidateContentDeliveryRequest | Validate delivery compliance |
| `get_media_buy_artifacts` | governance | GetMediaBuyArtifactsRequest | Get compliance artifacts |
| `sync_plans` | governance | SyncPlansRequest | Sync campaign plans |
| `check_governance` | governance | CheckGovernanceRequest | Check governance approval |
| `report_plan_outcome` | governance | ReportPlanOutcomeRequest | Report plan outcomes |
| `get_plan_audit_logs` | governance | GetPlanAuditLogsRequest | Get audit logs |
| `si_get_offering` | sponsored_intelligence | SiGetOfferingRequest | Get SI offering details |
| `si_initiate_session` | sponsored_intelligence | SiInitiateSessionRequest | Start SI session |
| `si_send_message` | sponsored_intelligence | SiSendMessageRequest | Send SI message |
| `si_terminate_session` | sponsored_intelligence | SiTerminateSessionRequest | End SI session |

## Response Builders

Import from `adcp.server.responses`:

| Function | Returns | Use With |
|---|---|---|
| `capabilities_response(protocols)` | Capabilities dict | `get_adcp_capabilities` |
| `products_response(products)` | Products dict | `get_products` |
| `media_buy_response(id, packages)` | Media buy dict | `create_media_buy` |
| `update_media_buy_response(id, packages)` | Updated buy dict | `update_media_buy` |
| `media_buys_response(media_buys)` | Media buys list | `get_media_buys` |
| `delivery_response(delivery)` | Delivery metrics | `get_media_buy_delivery` |
| `creative_formats_response(formats)` | Formats list | `list_creative_formats` |
| `sync_creatives_response(creatives)` | Sync results | `sync_creatives` |
| `list_creatives_response(creatives)` | Creatives list | `list_creatives` |
| `build_creative_response(manifest)` | Creative manifest | `build_creative` |
| `preview_creative_response(previews)` | Preview URLs | `preview_creative` |
| `signals_response(signals)` | Signals list | `get_signals` |
| `activate_signal_response(signal)` | Activation result | `activate_signal` |
| `sync_accounts_response(accounts)` | Sync results | `sync_accounts` |
| `log_event_response(events)` | Log results | `log_event` |
| `sync_catalogs_response(catalogs)` | Catalog results | `sync_catalogs` |
| `error_response(code, message)` | Error dict | Any handler |
| `media_buy_error_response(errors)` | Error dict | `create/update_media_buy` |

## Type Guards

Import from `adcp.types.guards` or `adcp`:

```python
from adcp import is_adcp_success, is_adcp_error

if is_adcp_success(response):
print(response.media_buy_id) # type-narrowed
else:
print(response.errors)
```

## Common Error Codes

| Code | Meaning | Recovery |
|---|---|---|
| `NOT_SUPPORTED` | Agent doesn't implement this task | Check capabilities first |
| `INVALID_BUDGET` | Budget below minimum | Increase to minimum |
| `INVALID_REQUEST` | Malformed request | Fix request params |
| `MISSING_CREATIVE` | No creatives attached | Call sync_creatives first |
| `MEDIA_BUY_NOT_FOUND` | Unknown media buy ID | Verify ID from create response |
| `UNAUTHORIZED` | Missing/invalid auth | Check auth token |
| `RATE_LIMITED` | Too many requests | Retry with backoff |

## DX Helpers (adcp.server.helpers)

Eliminate boilerplate in handler code. Import from `adcp.server` or `adcp.server.helpers`.

### Error Responses

```python
from adcp.server import adcp_error

# Auto-recovery from 20+ standard codes (transient/correctable/terminal)
return adcp_error("BUDGET_TOO_LOW", "Budget $50 is below $500 minimum",
field="budget", suggestion="Increase to at least $500")

return adcp_error("RATE_LIMITED", retry_after=30)

return adcp_error("PRODUCT_NOT_FOUND",
field="product_id",
suggestion="Use get_products to discover available products")
```

### Media Buy State Machine

```python
from adcp.server import valid_actions_for_status, is_terminal_status
from adcp.server.responses import media_buy_response

# Response builders auto-populate valid_actions from status
resp = media_buy_response("mb_1", packages, status="active")
# resp["valid_actions"] = ["pause", "cancel", "update_budget", ...]
# resp["revision"] = 1 (auto-set)
# resp["confirmed_at"] = "2026-..." (auto-set)
```

### Account Resolution

```python
from adcp.server import resolve_account

async def create_media_buy(self, params, context=None):
account, error = await resolve_account(params, self.find_account)
if error:
return error # Auto-formatted ACCOUNT_NOT_FOUND response
# account is guaranteed non-None here
```

### Context Passthrough

```python
from adcp.server import inject_context

async def get_products(self, params, context=None):
response = {"products": my_products}
return inject_context(params, response) # Echoes params.context back
```

### Cancellation

```python
from adcp.server import cancel_media_buy_response

# Auto-sets canceled_at=now, status="canceled", valid_actions=[]
return cancel_media_buy_response("mb_1", "buyer", reason="Campaign ended")
```

### Decorator Builder (auto-capabilities)

```python
from adcp.server import adcp_server, serve

server = adcp_server("my-seller")

@server.get_products
async def get_products(params, context=None):
return products_response(MY_PRODUCTS)

# get_adcp_capabilities auto-generated from registered handlers
serve(server, name="my-seller")
```

## Import Quick Reference

```python
# Client setup
from adcp import ADCPClient, ADCPMultiAgentClient, AgentConfig

# Request/response types
from adcp.types import GetProductsRequest, CreateMediaBuyRequest, Product, Package

# Response variant types (discriminated unions)
from adcp.types.aliases import CreateMediaBuySuccessResponse, CreateMediaBuyErrorResponse

# Type guards
from adcp.types.guards import is_create_media_buy_success, is_adcp_error

# Server framework
from adcp.server import ADCPHandler, serve
from adcp.server.responses import capabilities_response, products_response

# DX helpers
from adcp.server import adcp_error, valid_actions_for_status, resolve_account
from adcp.server import inject_context, cancel_media_buy_response

# Testing
from adcp.testing import test_agent, creative_agent
```
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ regenerate-schemas: ## Download latest schemas and regenerate models
$(PYTHON) scripts/fix_schema_refs.py
@echo "Generating Pydantic models..."
$(PYTHON) scripts/generate_types.py
@echo "Consolidating exports..."
$(PYTHON) scripts/consolidate_exports.py
@echo "Generating ergonomic coercion..."
$(PYTHON) scripts/generate_ergonomic_coercion.py
@echo "✓ Schemas regenerated successfully"

validate-generated: ## Validate generated code (syntax and imports)
Expand Down
77 changes: 77 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# adcp - Python SDK for Ad Context Protocol

## What this is
Python client and server library for ADCP, the agent-to-agent advertising protocol.
Two use cases: (1) connect to ADCP agents as a buyer, (2) build ADCP agents as a seller.

## Quick start: Connect as a buyer
```python
from adcp.testing import test_agent

products = await test_agent.simple.get_products(
brief="Coffee subscription service",
buying_mode="brief",
)
print(f"Found {len(products.products)} products")
```

## Quick start: Build a seller agent
```python
from adcp.server import ADCPHandler, serve
from adcp.server.responses import capabilities_response, products_response

class MySeller(ADCPHandler):
async def get_adcp_capabilities(self, params, context=None):
return capabilities_response(["media_buy"])

async def get_products(self, params, context=None):
return products_response([
{"product_id": "p1", "name": "Premium Display",
"pricing_options": [{"pricing_model": "cpm", "floor_price": 5.0, "currency": "USD"}]}
])

serve(MySeller(), name="my-seller")
```

## Key modules
- `adcp.client`: ADCPClient, ADCPMultiAgentClient (buyer-side)
- `adcp.server`: ADCPHandler, serve, adcp_server, response builders (seller-side)
- `adcp.server.helpers`: adcp_error, valid_actions_for_status, resolve_account, inject_context
- `adcp.types`: All Pydantic types (Product, MediaBuy, Creative, etc.)
- `adcp.types.guards`: Type guards (is_adcp_success, is_adcp_error, typed per-response guards)
- `adcp.types.aliases`: Semantic names for discriminated union variants
- `adcp.testing`: Pre-configured test agents (test_agent, creative_agent)
- `adcp.exceptions`: Error hierarchy (ADCPError, ADCPTaskError with is_retryable)
- `adcp.capabilities`: FeatureResolver, build_synthetic_capabilities, validate_capabilities

## Framework auto-behaviors (seller-side)
- adcp_error("CODE") auto-populates recovery classification from 20+ standard codes
- media_buy_response(..., status="active") auto-populates valid_actions
- resolve_account(params, resolver) auto-returns ACCOUNT_NOT_FOUND errors
- inject_context(params, response) auto-echoes context passthrough
- cancel_media_buy_response() auto-sets canceled_at, status, valid_actions
- Decorator builder (adcp_server) auto-generates get_adcp_capabilities

## Import conventions
```python
from adcp import ADCPClient, AgentConfig # Client setup
from adcp.types import Product, GetProductsRequest # Domain types
from adcp.types.aliases import CreateMediaBuySuccessResponse # Union variants
from adcp.types.guards import is_adcp_success # Response handling
from adcp.server import ADCPHandler, serve # Server setup
from adcp.server import adcp_error, resolve_account # DX helpers
from adcp.server.responses import products_response # Response builders
```

## Validation
```bash
pytest tests/ -v # Tests
ruff check src/ # Linter
mypy src/adcp/ # Type checker
```

## Do NOT read (generated, wastes context)
- `src/adcp/types/generated_poc/` - auto-generated from schemas
- `src/adcp/types/_generated.py` - consolidated generated exports
- `src/adcp/types/_ergonomic.py` - auto-generated coercion
- `schemas/` - raw JSON schemas
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ warn_unused_configs = true
module = "tests.*"
disable_error_code = ["import-not-found", "no-untyped-def", "var-annotated", "operator"]

[[tool.mypy.overrides]]
module = "adcp.types.generated_poc.*"
disable_error_code = ["valid-type"]

[[tool.mypy.overrides]]
module = "tests.integration.*"
ignore_errors = true
Expand Down
Loading
Loading