Глава 27

A2A-client начинает с card, затем отправляет Message

Клиент A2A никогда не начинает с отправки сообщения - он начинает с card. ClientFactory разрешает Agent Card по URL, выбирает совместимый interface и создаёт типизированный client. И только потом запрос указывает принимаемые output modes и решает, ждать результат или получить немедленный ответ.

Рабочий клиент из демо проходит весь путь: разрешить card, отправить сообщение, проверить, что вернулась именно Task.

TypeScript
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 и решение.

JSON
{
  "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 станет дверью внутрь периметра.

Клиент и сервер разговаривают. Осталось решить, как именно он узнаёт об обновлениях долгой задачи - и это осознанный выбор из трёх механизмов.

Ссылки