@@ -204,6 +204,16 @@ <h2 class="section-title" id="header-classes">Classes</h2>
204204 try:
205205 result = await self._tool_callers[skill_name](params)
206206 await self._send_result(event_queue, context, skill_name, result)
207+ except ADCPError as exc:
208+ # Application-layer AdCP error (IdempotencyConflictError etc.).
209+ # Emit a failed task with the adcp_error in a DataPart per
210+ # transport-errors.mdx §A2A Binding, plus a human-readable text
211+ # part. The JSON-RPC channel is reserved for transport-level
212+ # errors (auth rejected, rate-limited pre-dispatch).
213+ logger.info(
214+ "AdCP application error for skill %s: %s", skill_name, exc
215+ )
216+ await self._send_adcp_error(event_queue, context, exc)
207217 except Exception:
208218 logger.exception("Error executing skill %s", skill_name)
209219 await self._send_error(
@@ -310,6 +320,44 @@ <h2 class="section-title" id="header-classes">Classes</h2>
310320 state=TaskState.failed,
311321 message=error_msg,
312322 )
323+ await event_queue.enqueue_event(task)
324+
325+ async def _send_adcp_error(
326+ self,
327+ event_queue: EventQueue,
328+ context: RequestContext,
329+ exc: ADCPError,
330+ ) -> None:
331+ """Publish a failed task carrying an AdCP ``adcp_error`` payload.
332+
333+ Follows transport-errors.mdx §A2A Binding: failed task with artifact
334+ containing a ``DataPart`` keyed under ``adcp_error`` plus a terse
335+ ``TextPart`` for human/LLM consumption.
336+ """
337+ # Derive the spec error code. ADCPTaskError carries a list of codes
338+ # (e.g. IdempotencyConflictError → IDEMPOTENCY_CONFLICT); fall back
339+ # to a generic INTERNAL_ERROR when the exception doesn't supply one.
340+ code = "INTERNAL_ERROR"
341+ if isinstance(exc, ADCPTaskError) and exc.error_codes:
342+ code = str(exc.error_codes[0])
343+
344+ adcp_error: dict[str, Any] = {
345+ "code": code,
346+ "message": exc.message,
347+ }
348+ recovery = STANDARD_ERROR_CODES.get(code, {}).get("recovery")
349+ if recovery:
350+ adcp_error["recovery"] = recovery
351+ suggestion = getattr(exc, "suggestion", None)
352+ if suggestion:
353+ adcp_error["suggestion"] = suggestion
354+
355+ task = _make_task(
356+ context,
357+ state=TaskState.failed,
358+ data={"adcp_error": adcp_error},
359+ message=exc.message,
360+ )
313361 await event_queue.enqueue_event(task)</ code > </ pre >
314362</ details >
315363< div class ="desc "> < p > Bridges ADCPHandler methods to the a2a-sdk AgentExecutor interface.</ p >
@@ -391,6 +439,16 @@ <h3>Methods</h3>
391439 try:
392440 result = await self._tool_callers[skill_name](params)
393441 await self._send_result(event_queue, context, skill_name, result)
442+ except ADCPError as exc:
443+ # Application-layer AdCP error (IdempotencyConflictError etc.).
444+ # Emit a failed task with the adcp_error in a DataPart per
445+ # transport-errors.mdx §A2A Binding, plus a human-readable text
446+ # part. The JSON-RPC channel is reserved for transport-level
447+ # errors (auth rejected, rate-limited pre-dispatch).
448+ logger.info(
449+ "AdCP application error for skill %s: %s", skill_name, exc
450+ )
451+ await self._send_adcp_error(event_queue, context, exc)
394452 except Exception:
395453 logger.exception("Error executing skill %s", skill_name)
396454 await self._send_error(
0 commit comments