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:
- DataPart em
message.parts:{"kind": "data", "data": {...}}. Preferido. message.metadata, com as chaves soltas ou aninhadas sob a URI da extensão.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 semkind, métodosSendMessage/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