Documentation Index
Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
Use this file to discover all available pages before exploring further.
このドキュメントは、A2A プロトコルで送信される AdCP レスポンスの 標準構造 を定義します。
必須構造
最終レスポンス(status: “completed”)
A2A 上の AdCP レスポンスは必ず以下を満たす必要があります:
- タスクのペイロードを含む
DataPart(kind: ‘data’)を少なくとも 1 つ含めます
- 複数アーティファクトではなく、1 つのアーティファクトに複数パートを入れる
DataPart が複数ある場合は最後のものを正とします
- AdCP ペイロードをフレームワーク固有オブジェクトでラップしない(
{ response: {...} } など禁止)
Recommended pattern:
{
"status": "completed",
"taskId": "task_123",
"contextId": "ctx_456",
"artifacts": [{
"name": "task_result",
"parts": [
{
"kind": "text",
"text": "Found 12 video products perfect for pet food campaigns"
},
{
"kind": "data",
"data": {
"products": [...],
"total": 12
}
}
]
}]
}
- TextPart (kind: ‘text’): 人間向けサマリー - 推奨(任意)
- DataPart (kind: ‘data’): 構造化された AdCP レスポンスペイロード - 必須
- FilePart (kind: ‘file’): 任意のファイル参照(プレビュー、レポート)
複数アーティファクト: 本質的に異なる成果物(例: クリエイティブと別個のトラフィッキングレポート)がある場合のみ。AdCP では稀であり、基本は 1 アーティファクト内に複数パートを推奨。
中間ステータスのレスポンスは、進捗把握のために任意で AdCP 構造化データを含められます。
{
"status": "working",
"taskId": "task_123",
"contextId": "ctx_456",
"artifacts": [{
"parts": [
{
"kind": "text",
"text": "Processing your request. Analyzing 50,000 inventory records..."
},
{
"kind": "data",
"data": {
"percentage": 45,
"current_step": "analyzing_inventory"
}
}
]
}]
}
中間レスポンスの特徴:
- TextPart はステータス表示のため推奨
- DataPart は任意だが、提供する場合は AdCP スキーマに準拠
- 中間ステータス用スキーマ(
*-async-response-working.json、*-async-response-input-required.json など)は策定中で変わる可能性あり
- スキーマ進化を踏まえ、中間データの扱いを緩やかにする選択も可能
最終ステータスになった場合(status: "completed" または status: "failed")、.artifacts 内に AdCP タスクレスポンスを含む DataPart が必須になります。
フレームワークのラッパー(禁止)
重要: DataPart の内容はフレームワーク固有オブジェクトでラップせず、AdCP レスポンスペイロードを直接含める必要があります。
// ❌ WRONG - Wrapped in custom object
{
"kind": "data",
"data": {
"response": { // ← Framework wrapper
"products": [...]
}
}
}
// ✅ CORRECT - Direct AdCP payload
{
"kind": "data",
"data": {
"products": [...] // ← Direct schema-compliant response
}
}
理由:
- スキーマ検証が破綻する(クライアントは
products がルートにあると期待)
- 不要なネストが増える
- プロトコル非依存設計に反する(ラッパーがフレームワーク依存)
- クライアントでのデータ抽出が複雑化
実装がラッパーを追加している場合、クライアントサイドで回避するのではなく、フレームワーク層のバグとして修正すべきです。
クライアントの標準的な扱い
このセクションでは、クライアントが A2A プロトコルレスポンスから AdCP レスポンスを抽出する方法を正確に定義します。
クイックリファレンス
| Status | Webhook Type | Data Location | Schema Required? | Returns |
|---|
working | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
submitted | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
input-required | TaskStatusUpdateEvent | status.message.parts[] | ✅ Yes (if present) | { status, taskId, message, data? } |
completed | Task | .artifacts[] only | ✅ Required | { status, taskId, message, data } |
failed | Task | .artifacts[] only | ✅ Required | { status, taskId, message, data } |
ポイント:
- 最終ステータス は
Task オブジェクトを用い、データは .artifacts に格納
- 中間ステータス は
TaskStatusUpdateEvent を用い、status.message.parts[] に任意データ
- いずれのステータスもデータがある場合は AdCP スキーマを使用
- 中間ステータスのスキーマは策定中で変わる可能性あり
ルール1: ステータスに応じた処理
Clients MUST branch on status field to determine the correct data extraction location:
function handleA2aResponse(response) {
const status = response.status;
// 中間ステータス - status.message.parts から抽出(TaskStatusUpdateEvent)
if (['working', 'submitted', 'input-required'].includes(status)) {
return {
status: status,
taskId: response.taskId,
contextId: response.contextId,
message: extractTextPartFromMessage(response),
data: extractDataPartFromMessage(response), // Optional AdCP data
};
}
// 最終ステータス - .artifacts から抽出(Task オブジェクト)
if (['completed', 'failed'].includes(status)) {
return {
status: status,
taskId: response.taskId,
contextId: response.contextId,
message: extractTextPartFromArtifacts(response),
data: extractDataPartFromArtifacts(response), // Required AdCP payload
};
}
throw new Error(`Unknown A2A status: ${status}`);
}
重要:
- 中間ステータス:
TaskStatusUpdateEvent → status.message.parts[] から抽出
- 最終ステータス:
Task オブジェクト → .artifacts[0].parts[] から抽出
Extract data from the appropriate location based on webhook type:
// For FINAL statuses (Task object) - extract from .artifacts
function extractDataPartFromArtifacts(response) {
const dataParts = response.artifacts?.[0]?.parts
?.filter(p => p.kind === 'data') || [];
if (dataParts.length === 0) {
throw new Error('Final response (completed/failed) missing required DataPart in artifacts');
}
// Use LAST data part as authoritative
const lastDataPart = dataParts[dataParts.length - 1];
const payload = lastDataPart.data;
// CRITICAL: Payload MUST be direct AdCP response
if (payload.response !== undefined && typeof payload.response === 'object') {
throw new Error(
'Invalid response format: DataPart contains wrapper object. ' +
'Expected direct AdCP payload (e.g., {products: [...]}) ' +
'but received {response: {products: [...]}}. ' +
'This is a server-side bug that must be fixed.'
);
}
return payload;
}
function extractTextPartFromArtifacts(response) {
const textPart = response.artifacts?.[0]?.parts?.find(p => p.kind === 'text');
return textPart?.text || null;
}
// For INTERIM statuses (TaskStatusUpdateEvent) - extract from status.message.parts
function extractDataPartFromMessage(response) {
const dataPart = response.status?.message?.parts?.find(p => p.data);
return dataPart?.data || null;
}
function extractTextPartFromMessage(response) {
const textPart = response.status?.message?.parts?.find(p => p.text);
return textPart?.text || null;
}
ルール3: スキーマ検証
すべての AdCP レスポンスはスキーマを用いますが、検証方法はステータスによって異なります:
function validateResponse(response, taskName) {
const status = response.status;
let data, schemaName;
// ステータスに応じてデータを抽出しスキーマを決定
if (['working', 'submitted', 'input-required'].includes(status)) {
// 中間: status.message.parts の任意データ
data = extractDataPartFromMessage(response);
if (data) {
// 中間ステータス専用スキーマ(策定中)
schemaName = `${taskName}-async-response-${status}.json`;
// 任意: スキーマが変わる可能性があるため中間検証を省略してもよい
if (STRICT_VALIDATION_MODE) {
validateAgainstSchema(data, loadSchema(schemaName));
}
}
} else if (['completed', 'failed'].includes(status)) {
// 最終: .artifacts から必須データ
data = extractDataPartFromArtifacts(response);
schemaName = `${taskName}-response.json`;
// 最終レスポンスは必ず検証
if (!validateAgainstSchema(data, loadSchema(schemaName))) {
throw new Error(
`Response payload does not match ${taskName} schema. ` +
`Ensure DataPart contains direct AdCP response structure.`
);
}
}
}
スキーマ進化の注意: 中間ステータスのスキーマ(*-async-response-working.json など)は策定中です。安定するまでは緩やかな扱いにする選択も可能です。
完全な例
Task と TaskStatusUpdateEvent の両方を正しく扱う統合例:
async function executeTask(taskName, params) {
const response = await a2aClient.send({
task: taskName,
params: params
});
// 1. ステータスに基づいて正しい場所から抽出
const result = handleA2aResponse(response);
// 2. スキーマ検証
validateResponse(response, taskName);
return result;
}
// 使い方
const result = await executeTask('get_products', {
brief: 'CTV inventory in California'
});
// ステータス別の処理
if (result.status === 'working') {
// TaskStatusUpdateEvent - data は status.message.parts
console.log('Processing:', result.message);
if (result.data) {
console.log('Progress:', result.data.percentage + '%');
}
} else if (result.status === 'input-required') {
// TaskStatusUpdateEvent - data from status.message.parts
console.log('Input needed:', result.message);
console.log('Reason:', result.data?.reason);
} else if (result.status === 'completed') {
// Task オブジェクト - data は .artifacts
console.log('Success:', result.message);
console.log('Products:', result.data.products); // Full AdCP response
}
Last Data Part Authority パターン
Test Cases
✅ Correct Behavior
// Test 1: Working status (TaskStatusUpdateEvent) - extract from status.message.parts
const workingResponse = {
status: 'working',
taskId: 'task_123',
contextId: 'ctx_456',
status: {
state: 'working',
message: {
role: 'agent',
parts: [
{ text: 'Processing inventory...' },
{ data: { percentage: 50, current_step: 'analyzing' } }
]
}
}
};
const result1 = handleA2aResponse(workingResponse);
assert(result1.data.percentage === 50, 'Should extract data from status.message.parts');
assert(result1.message === 'Processing inventory...', 'Should extract text from status.message.parts');
// Test 2: Completed status (Task) - extract from .artifacts
const completedResponse = {
status: 'completed',
taskId: 'task_123',
contextId: 'ctx_456',
status: {
state: 'completed',
timestamp: '2025-01-22T10:30:00Z'
},
artifacts: [{
parts: [
{ kind: 'text', text: 'Found 3 products' },
{ kind: 'data', data: { products: [...], total: 3 } }
]
}]
};
const result2 = handleA2aResponse(completedResponse);
assert(result2.data !== undefined, 'Completed status must have data');
assert(Array.isArray(result2.data.products), 'Data should be direct AdCP payload');
// Test 3: Wrapper detection (should reject)
const wrappedResponse = {
status: 'completed',
taskId: 'task_123',
artifacts: [{
parts: [
{ kind: 'data', data: { response: { products: [...] } } }
]
}]
};
assert.throws(() => {
extractDataPartFromArtifacts(wrappedResponse);
}, /Invalid response format.*wrapper/);
❌ Incorrect Behavior (Common Mistakes)
// 誤り: 中間ステータスで抽出元を間違える
function badHandleWorking(response) {
// ❌ TaskStatusUpdateEvent doesn't have .artifacts - data is in status.message.parts
const data = response.artifacts?.[0]?.parts?.find(p => p.kind === 'data')?.data;
return { status: 'working', data }; // Will be null/undefined!
}
// 誤り: completed で抽出元を間違える
function badHandleCompleted(response) {
// ❌ Task object has data in .artifacts, not in status.message.parts
const data = response.status?.message?.parts?.find(p => p.data)?.data;
return { status: 'completed', data }; // Will be null/undefined!
}
// 誤り: ラッパーを確認しない
function badExtraction(response) {
const payload = response.artifacts[0].parts[0].data;
// ❌ Returns { response: { products: [...] } } instead of { products: [...] }
return payload; // Client receives wrong structure!
}
// 誤り: ネストされた response を参照
function badClientUsage(result) {
// ❌ クライアントコードがこうする必要はない
const products = result.data.response.products;
// 正しくは: result.data.products
}
エラーハンドリング
タスクレベルのエラー(部分失敗)
タスクは実行されたが完全には完了しなかった場合。status: "completed" の DataPart に errors 配列を入れます:
{
"status": "completed",
"taskId": "task_123",
"artifacts": [{
"parts": [
{
"kind": "text",
"text": "Signal discovery completed with partial results"
},
{
"kind": "data",
"data": {
"signals": [...],
"errors": [{
"code": "NO_DATA_IN_REGION",
"message": "No signal data available for Australia",
"field": "deliver_to.countries[1]",
"details": {
"requested_country": "AU",
"available_countries": ["US", "CA", "GB"]
}
}]
}
}
]
}]
}
errors 配列を使う場面:
- プラットフォーム認可の問題(
PLATFORM_UNAUTHORIZED)
- データが部分的にしかない場合
- データの一部でバリデーション問題がある場合
プロトコルレベルのエラー(致命的)
タスクが実行できなかった場合。status: "failed" とメッセージを返します:
{
"taskId": "task_456",
"status": "failed",
"message": {
"parts": [{
"kind": "text",
"text": "Authentication failed: Invalid or expired API token"
}]
}
}
status: failed を使う場面:
- 認証失敗(無効/期限切れトークン)
- リクエスト不正(JSON 破損、必須フィールド欠落)
- リソース不在(未知の taskId、期限切れ context)
- システムエラー(DB 不調、内部サービス障害)
ステータスマッピング
AdCP は A2A の TaskState enum をそのまま使用します:
| A2A Status | Payload Type | Data Location | AdCP Usage |
|---|
completed | Task | .artifacts | Task finished successfully, data in DataPart, optional errors array |
failed | Task | .artifacts | Fatal error preventing completion, optional error details |
input-required | TaskStatusUpdateEvent | status.message.parts | Need user input/approval, data + text explaining what’s needed |
working | TaskStatusUpdateEvent | status.message.parts | Processing (< 120s), optional progress data |
submitted | TaskStatusUpdateEvent | status.message.parts | Long-running (hours/days), minimal data, use webhooks/polling |
Webhook ペイロード
非同期処理(status: "submitted")では Webhook でも同じアーティファクト構造を返します:
POST /webhook-endpoint
{
"taskId": "task_123",
"status": "completed",
"timestamp": "2025-01-22T10:30:00Z",
"artifacts": [{
"parts": [
{"kind": "text", "text": "Media buy approved and live"},
{"kind": "data", "data": {
"media_buy_id": "mb_456",
"packages": [...],
"creative_deadline": "2025-01-30T23:59:59Z"
}}
]
}]
}
AdCP データは同じ Last DataPart パターンで抽出します。Webhook 認証、リトライパターン、セキュリティ は Webhooks を参照してください。
レスポンス内の File Part
クリエイティブ系の操作ではファイル参照を含む場合があります:
{
"status": "completed",
"artifacts": [{
"parts": [
{"kind": "text", "text": "Creative uploaded and preview generated"},
{"kind": "data", "data": {
"creative_id": "cr_789",
"format_id": {
"agent_url": "https://creatives.adcontextprotocol.org",
"id": "video_standard_30s"
},
"status": "ready"
}},
{"kind": "file", "uri": "https://cdn.example.com/cr_789/preview.mp4", "name": "preview.mp4", "mimeType": "video/mp4"}
]
}]
}
File Part の用途: プレビュー URL、生成済みアセット、トラフィッキングレポート。AdCP レスポンスの生データには使わず、必ず DataPart を使用。
リトライと冪等性
TaskId による重複排除
A2A の taskId はリトライ検出に使えます。エージェントは次を行うべきです:
taskId が完了済みオペレーションと一致する場合(TTL 内)、キャッシュレスポンスを返す
- 進行中のオペレーションに対する重複
taskId 送信は拒否します
// Duplicate taskId during active operation
{
"taskId": "task_123",
"status": "failed",
"message": {
"parts": [{
"kind": "text",
"text": "Task 'task_123' is already in progress. Use tasks/get to check status."
}]
}
}
Implementation Checklist
When implementing A2A responses for AdCP:
Final Responses (status: “completed” or “failed”) - Use Task object:
Interim Responses (status: “working”, “submitted”, “input-required”) - Use TaskStatusUpdateEvent:
Error Handling:
General:
See Also