Skip to content

Integração A2A — Para Parceiros

O que é o protocolo A2A?

O Agent-to-Agent (A2A) é um protocolo que permite que seu agente de IA se comunique com o agente iFriend através de JSON-RPC 2.0 sobre HTTP.

Quando usar?

  • Seu agente de IA precisa de informações de turismo que a iFriend possui
  • Você quer que seus usuários façam reservas de experiências
  • Você quer oferecer serviços da iFriend como parte do seu ecossistema
  • Você quer integrar o iFriend com seu sistema de agentes (Agentforce, OpenAI Agents, LangGraph, CrewAI, Semantic Kernel, etc.)

Arquitetura de Segurança

Partner Agent
    |
    | HTTPS + JWT Bearer
    | Authorization: Bearer <token>
    v
Cloud Run (A2AJWTAuthMiddleware valida assinatura, expiração e role ROLE_A2A_USER)
    |
    v
Google ADK Agent (iFriend)
  • O endpoint A2A é exposto diretamente pelo serviço Cloud Run, protegido pelo A2AJWTAuthMiddleware.
  • Toda requisição (exceto o Agent Card, que é público) exige um JWT válido com a role ROLE_A2A_USER.

Quick Start

1. Receba as credenciais

A equipe iFriend provisiona uma conta de serviço dedicada para o seu agente (email + senha), com a role ROLE_A2A_USER. Você vai receber:

  • A2A_URL — URL do endpoint A2A (ex: https://agents.theifriend.com/trip/a2a)
  • email / password — credenciais da conta de serviço

2. Obtenha o Agent Card (público, sem autenticação)

curl https://agents.theifriend.com/trip/a2a/.well-known/agent-card.json

Você receberá algo como:

{
  "name": "ifriend_agent",
  "version": "3.3.6",
  "protocolVersion": "0.2.6",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "extensions": [
      {"uri": "https://theifriend.com/a2a/extensions/caller-context/v1", "required": false}
    ]
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "skills": [
    {"id": "discovery", "name": "Busca de experiências e guias"},
    {"id": "itinerary", "name": "Montagem de roteiros"},
    {"id": "quote", "name": "Cotação de preços"},
    {"id": "booking", "name": "Reservas"},
    {"id": "payment", "name": "Pagamento"},
    {"id": "support", "name": "Atendimento humano"},
    {"id": "custom_tour", "name": "Tour personalizado"},
    {"id": "booking_info", "name": "Consulta de reservas"}
  ]
}

3. Autentique e obtenha o JWT

O mesmo endpoint de autenticação usado por toda a API iFriend — não é um mecanismo especial criado para A2A:

curl -X POST https://api.theifriend.com/authentication_token \
  -H "Content-Type: application/json" \
  -d '{"email": "SEU_EMAIL", "password": "SUA_SENHA"}'

Resposta:

{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Sobre expiração: não há um TTL único documentado — trate o token como de vida curta/média e implemente reautenticação automática ao receber 401 Unauthorized, em vez de confiar num tempo fixo.

4. Envie uma mensagem

curl -X POST https://agents.theifriend.com/trip/a2a/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SEU_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "messageId": "msg-001",
        "parts": [{"kind": "text", "text": "Quero experiências em São Paulo"}]
      }
    }
  }'

messageId é obrigatório (gere um UUID por mensagem). Para continuar uma conversa multi-turn, inclua "contextId" (retornado na resposta anterior) e, se quiser continuar uma task específica, "taskId" — ambos dentro do objeto message, não em params.

5. Receba a resposta

{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "contextId": "a1b2c3d4-...",
    "kind": "task",
    "status": {
      "state": "completed",
      "message": {
        "role": "agent",
        "parts": [{"kind": "text", "text": "Encontrei várias experiências em São Paulo..."}]
      }
    },
    "artifacts": []
  }
}

A resposta é um objeto Task (kind: "task") — o id é o identificador da task (use em tasks/get/tasks/cancel como id, não taskId), e o texto do agente vem em status.message.parts e/ou em artifacts[].parts.

6. (Opcional) Envie o contexto do chamador

O protocolo A2A não define um formato para "contexto antes da mensagem". O Trip publica o próprio contrato no Agent Card: a extensão https://theifriend.com/a2a/extensions/caller-context/v1, opcional. Ela serve para quem chama o Trip em nome de um cliente final, como um agente de CRM no WhatsApp. O schema completo está em capabilities.extensions[0].params do Agent Card.

Contexto do chamador (extensão caller-context/v1)

Envie o contexto em um destes lugares, em ordem de precedência:

  1. DataPart em message.parts: {"kind": "data", "data": {...}}. Preferido.
  2. message.metadata, com as chaves soltas ou aninhadas sob a URI da extensão.
  3. params.metadata.
{
  "jsonrpc": "2.0", "id": "1", "method": "message/send",
  "params": {
    "message": {
      "role": "user", "messageId": "msg-002", "contextId": "sessao-crm-123",
      "parts": [
        {"kind": "text", "text": "Cliente quer passeio em Lisboa dia 12/11"},
        {"kind": "data", "data": {
          "caller": "agentforce",
          "caseId": "500…", "contactId": "003…", "accountId": "001…", "leadId": "00Q…",
          "canal": "WhatsApp", "idioma": "pt-BR",
          "cliente": {"nome": "Maria", "email": "maria@exemplo.com", "telefone": "5511999990000"},
          "conta": {"tipo": "viajante", "existente": true}
        }}
      ]
    }
  }
}
Campo Aliases aceitos Uso no Trip
caller origem, source, agent Identifica o agente chamador
case_id, contact_id, account_id, lead_id caseId, contactId, accountId, leadId Rastreio (IDs do CRM)
canal channel Canal do cliente final
idioma language, locale, lang Idioma da resposta
cliente.{nome,email,telefone} customer/contact, name, phone, whatsapp O Trip não pede de novo esses dados
conta.{tipo,existente,empresa,cnpj} account, type, company tipo: viajante ou agencia

Como o Trip se comporta:

  • Sem contexto, o Trip funciona normalmente, só com o texto.
  • Campos desconhecidos no DataPart são guardados (extra) e não causam erro.
  • Quando chamado via A2A, o Trip não faz handoff para atendimento humano, porque quem qualifica e encaminha é o agente chamador. Se não houver produto, ele responde isso claramente.
  • Formato protobuf/A2A v1 ("role": "ROLE_USER", content: [...], parts sem kind, métodos SendMessage/SendStreamingMessage): aceito. O Trip normaliza para o formato v0.3 antes da validação.

Métodos disponíveis

Todos são chamados via JSON-RPC 2.0 (POST no mesmo endpoint /, método indicado no campo "method" do corpo):

Método Descrição
message/send Envia mensagem e inicia/continua uma task (multi-turn)
message/stream Igual a message/send, mas com resposta em streaming (SSE)
tasks/get Consulta o resultado de uma task (params: {"id": "<task_id>"})
tasks/cancel Cancela uma task em andamento (params: {"id": "<task_id>"}) — cancelamento é cooperativo: nunca falha, mas não interrompe instantaneamente uma resposta já em geração

Endpoints

Endpoint Método Descrição
.../a2a/.well-known/agent-card.json GET Agent Card (público, sem auth)
.../a2a/ POST Todos os métodos JSON-RPC acima

Não há endpoints REST separados por task (ex: GET /tasks/{id}) — tudo é JSON-RPC no mesmo endpoint.

Exemplos de Código

Consulte Exemplos para código em Python, JavaScript e cURL, e os scripts executáveis em examples/a2a_test_cli.py, examples/a2a_echo_agent.py e examples/a2a_outbound_roundtrip_test.py no repositório da iFriend para testar localmente.

Suporte

Em caso de dúvidas: suporte@ifriend.com