A2A-client начинает с card, затем отправляет Message
Клиент A2A никогда не начинает с отправки сообщения - он начинает с card. ClientFactory разрешает Agent Card по URL, выбирает совместимый interface и создаёт типизированный client. И только потом запрос указывает принимаемые output modes и решает, ждать результат или получить немедленный ответ.
Рабочий клиент из демо проходит весь путь: разрешить card, отправить сообщение, проверить, что вернулась именно Task.
export async function runA2ADemo(messageText = "Проверь релиз web-portal@2.4.0"): Promise<A2ADemoResult> {
const running = await startA2AServer();
try {
const client = await new ClientFactory().createFromUrl(running.baseUrl);
const request: SendMessageRequest = {
tenant: "",
message: userMessage(messageText),
configuration: {
acceptedOutputModes: ["text/markdown", "application/json"],
taskPushNotificationConfig: undefined,
returnImmediately: false
},
metadata: { traceId: randomUUID() }
};
const result = await client.sendMessage(request);
if (!isTask(result) || !result.status) {
throw new Error("A2A agent returned a Message instead of a Task");
}
return {
agentName: running.card.name,
state: result.status.state,
taskId: result.id,
artifactName: result.artifacts[0]?.name ?? "missing",
decision: readDecision(result)
};
} finally {
await running.close();
}Результат детерминирован и его можно проверять в тесте - агент, состояние, taskId, имя artifact и решение.
{
"agentName": "Release Review Agent",
"state": "completed",
"taskId": "<generated UUID>",
"artifactName": "release-review.md",
"decision": "ready"
}До SendMessage клиент обязан пройти по собственной политике - это не бюрократия, а защита от чужого и вредоносного.
- Проверить версию протокола и binding.
- Проверить required extensions и опциональные capabilities.
- Сопоставить media modes с локальными декодерами.
- Применить объявленную схему аутентификации.
- Ограничить redirect и card URL policy.
- Сохранить taskId/contextId отдельно от UI message id.
После ответа дисциплина не заканчивается. Результат может быть Message или Task - используйте type guard, затем проверяйте терминальное или interrupted-состояние и media type каждого artifact Part. Не исполняйте data Part как код и не доверяйте filename при сохранении.
Card URL является внешним вводом. Agent discovery может привести клиента к внутреннему адресу или неожиданному redirect. Применяйте SSRF-политику, TLS и allowlist в корпоративных оркестраторах - иначе discovery станет дверью внутрь периметра.
Клиент и сервер разговаривают. Осталось решить, как именно он узнаёт об обновлениях долгой задачи - и это осознанный выбор из трёх механизмов.