A2A Backend Adapter
The optional A2A Backend Adapter connects one remote A2A agent to the existing BackendPort. It uses the official A2A JavaScript SDK for Agent Card discovery, protocol negotiation, messages, Tasks, cancellation, and Artifact decoding. A2A objects remain private to the adapter; the Gateway, Task runtime, frontend, and clients continue to use their protocol-neutral contracts.
This is a programmatic extension for custom Gateway launchers. It does not add an AGENT_PROTOCOL value or a Desktop setting.
Connect an agent
import { createGatewayApplication } from 'qwen-audio-agent/gateway-application'
import { createBackendAgentHost } from 'qwen-audio-agent/backend-adapter-sdk'
import {
createA2ABackendAdapter,
} from 'qwen-audio-agent/a2a-backend-adapter'
const backend = createA2ABackendAdapter({
agentCardUrl: 'https://agent.example/.well-known/agent-card.json',
token: process.env.MY_A2A_TOKEN,
})
const agent = createBackendAgentHost(backend)
const application = createGatewayApplication({ agent })
process.once('SIGTERM', () => application.close())agentCardUrl is the complete public Agent Card URL. It must use HTTP or HTTPS and cannot contain credentials. Use token for Bearer authentication, or headers for another scheme:
const backend = createA2ABackendAdapter({
agentCardUrl: process.env.MY_A2A_AGENT_CARD_URL,
headers: async () => ({
Authorization: `Bearer ${await refreshAccessToken()}`,
'X-Tenant': 'tenant-one',
}),
})Configured headers are applied to both discovery and task requests but are never returned by describe(). An in-memory standard agentCard can replace URL discovery. JSON-RPC and HTTP+JSON/REST are supported; the first compatible interface declared by the Agent Card is selected. A2A 0.3 compatibility is enabled by default through the official SDK and can be disabled with legacyCompat: false.
Task projection
- The canonical Task instruction becomes the user Message text; frontend history, memory, Gateway IDs, and routing metadata are not sent remotely.
- Input attachments become standard A2A raw or URL Parts with MIME types.
- When the Agent Card advertises streaming, the adapter consumes native events; otherwise it requests non-blocking execution and polls
GetTask. - A2A status, Message, and Artifact updates become
backend.activity,backend.message, andbackend.artifact, continuously updating one Gateway Task. - Final Artifacts become standard Gateway Artifacts; the final agent status Message supplies natural speech material.
CancelTaskis used when the remote task ID is known. Local cancellation still terminates a request that has not received a Task ID yet.
The Gateway taskId never becomes a remote task identity. The mapping exists only while a submission is active and remote IDs do not cross BackendPort.
Conversation continuity
By default, each new Task starts a separate context. With reuseContext: true, the first request omits contextId; subsequent Tasks for the same ownerId reuse the opaque ID returned by the server. New work still starts a new Task; taskId is sent only to continue an interrupted Task. Concurrent first requests wait for context discovery, not for the first Task to finish.
The remote Agent implements history retention; contextId does not carry history, and historyLength only limits history returned by A2A responses. Frontend chat history is not forwarded. Work with continuity: 'isolated' always starts a fresh context without changing the owner's mapping.
The in-memory cache targets 100 owner contexts; active contexts are not evicted and can temporarily exceed this limit. Closing the adapter clears the mapping. Servers that return no context ID continue to receive fresh requests. The cockpit and customer-service examples enable reuse and retain up to 50 turns inside their Agents; other integrations remain opt-in.
State mapping
| A2A Task state | Backend state |
|---|---|
SUBMITTED | submitted |
WORKING / UNSPECIFIED | working |
COMPLETED | completed outcome |
FAILED / REJECTED | failed outcome |
CANCELED | cancelled outcome |
INPUT_REQUIRED | input_required; wait for respondInput and continue the same remote Task |
AUTH_REQUIRED | auth_required; surface the remote request and continue the same remote Task after input |
A2A does not assign universal credential or approval semantics after AUTH_REQUIRED. The adapter therefore exposes the Agent's status message as a protocol-neutral input request; it never guesses credentials. The answer is sent as a new user Message carrying the same remote Task and Context IDs, while the Gateway keeps the original Task active.
Options
agentCardUrloragentCard: exactly one discovery source is required;token,headers,fetchImpl: authentication and transport hooks;acceptedOutputModes: requested result MIME types;pollIntervalMs: task polling interval, default 1 second;timeoutMs: optional per-Task timeout; disabled by default so protocol Tasks can run until completion or explicit cancellation;requestTimeoutMs: timeout for unscoped requests such as Agent Card discovery, default 30 seconds;legacyCompat: official A2A 0.3 compatibility, default enabled;reuseContext: reuse an owner-scoped Context across new Tasks, defaultfalse;clientFactory: test or advanced transport injection.
Run the public Backend Adapter conformance suite for any derived adapter. The built-in A2A adapter itself is covered by conformance tests plus A2A 1.0 HTTP+JSON discovery, task round-trip, and streaming-event tests.