Appeler l'agent par API

L'agent expose une API A2A (Agent-to-Agent) au format JSON-RPC 2.0 sur un seul point d'entrée POST /. Ce guide explique, en langage clair, comment l'appeler : les arguments attendus, la sécurité, et les types d'appel supportés.

→ Pour une référence interactive (essayer les appels en direct), ouvrez la page Swagger / OpenAPI.

Vue d'ensemble

Tout passe par une seule URL et une seule méthode HTTP : on envoie un objet JSON-RPC en POST, et le champ method de cet objet décide de l'action (envoyer un message, récupérer une tâche, l'annuler…).

POST   http://<hôte>:<port>/

Content-Type : application/json  •  Corps : enveloppe JSON-RPC 2.0  •  Réponse : JSON (ou flux SSE pour le streaming).

Le minimum vital Un appel valide contient toujours jsonrpc: "2.0", un id (numéro ou chaîne), un method et un objet params.

Deux déploiements : Portail vs Agent

Le même cœur logique est exposé de deux façons. Choisissez selon que l'appelant est un humain (navigateur) ou une machine (autre agent / script).

  Portail (IHM) Agent autonome
Pour qui Un humain, via le navigateur Une autre machine / un autre agent
Port par défaut 8000 (PORT) 8001 (AGENT_PORT)
Authentification Connexion interactive Microsoft 365 (MSAL) Jeton utilisateur Microsoft 365 x-m365-id-token
Sert l'interface web Oui (HTML/CSS/JS sous public/) Non — uniquement l'API
Point d'entrée API Identique : POST / + JSON-RPC 2.0
À retenir Pour intégrer l'agent dans un autre programme, visez l'agent autonome (port 8001). Le portail, lui, est conçu pour l'usage humain et ajoute une connexion interactive.

Découverte : l'Agent Card

Avant d'appeler l'agent, un client peut lire sa carte d'identité (l'Agent Card) pour connaître son nom, ses capacités et la liste de ses skills.

GET   /.well-known/agent-card.json
// Réponse (extrait)
{
  "name": "A2A Agent",
  "description": "An A2A agent backed by Azure AI Foundry…",
  "version": "1.0.0",
  "supportedInterfaces": [
    { "url": "http://localhost:8001",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0" }
  ],
  "capabilities": { "streaming": true, "pushNotifications": false },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [ /* … cartes de chaque skill … */ ]
}

Authentification & sécurité

1. Liaison réseau (binding)

Par défaut, l'agent n'écoute que sur 127.0.0.1 (boucle locale) : il n'est pas accessible depuis le réseau. Pour l'exposer, on définit HOST=0.0.0.0 — l'agent standalone exige toujours un x-m365-id-token valide, vérifié côté serveur.

Avertissement de sécurité Ne désactivez pas M365_REQUIRE_AUTH et ne lancez pas l'agent standalone sans M365_CLIENT_ID configuré : le serveur refuse ce mode car il supprimerait la vérification utilisateur.

2. Jeton utilisateur Microsoft 365

Chaque appel à l'agent standalone doit présenter un id_token Microsoft Entra ID émis pour cette application. Le serveur vérifie la signature, l'expiration, l'audience et le tenant avant tout traitement.

x-m365-id-token: <id_token obtenu après authentification utilisateur>

En cas de jeton manquant ou invalide, l'agent répond HTTP 401 avec l'en-tête WWW-Authenticate: Bearer realm="m365" et une erreur JSON-RPC de code -32010.

3. Autres protections en place

Protection Détail
En-têtes de sécurité Ajoutés à chaque réponse ; X-Powered-By désactivé.
Taille du corps Limitée à 8 Mo par défaut (JSON_BODY_LIMIT) — assez pour une image réduite jointe, mais borné pour éviter les abus.
Proxy de confiance trust proxy réglé sur loopback par défaut (TRUST_PROXY).
Jetons jamais persistés Les jetons utilisateur transmis ne vivent que le temps de la requête (jamais écrits sur disque).

Jetons délégués (skills authentifiés)

Certains skills agissent au nom de l'utilisateur (par ex. lire son agenda Microsoft 365 ou publier sur un blog). L'appelant peut transmettre les jetons correspondants via des en-têtes dédiés. Ils sont propagés aux skills le temps de la requête, puis oubliés.

En-tête Usage
x-m365-access-token Jeton Microsoft Graph (agenda, courrier…). Si absent, un repli par variable d'environnement peut s'appliquer.
x-blog-access-token Jeton pour les skills du blog familial.
Bon à savoir Ces en-têtes sont optionnels. Sans eux, les skills non authentifiés (météo, actualités, génération d'image, compteurs de coût…) fonctionnent normalement.

Types d'appel supportés

Quatre méthodes JSON-RPC sont disponibles. Le champ method de l'enveloppe choisit l'action.

method Réponse Rôle
SendMessage JSON Envoie un message et reçoit la réponse complète d'un coup (cas le plus courant).
SendStreamingMessage SSE Idem mais en flux Server-Sent Events : événements task → artifact → status.
GetTask JSON Récupère une tâche déjà produite à partir de son identifiant.
CancelTask JSON Demande l'annulation d'une tâche.

Structure d'un message

Pour SendMessage et SendStreamingMessage, le paramètre obligatoire est params.message. Un message est composé de parts : du texte, et/ou un fichier (image).

Champ Obligatoire Description
message.role Oui Toujours "ROLE_USER" côté appelant.
message.parts Oui Tableau (non vide) de parts — voir ci-dessous.
message.contextId Non Identifiant de conversation. Réutilisez-le pour garder l'historique (mémoire) d'un échange à l'autre. Généré automatiquement si absent.
message.messageId Non Identifiant du message (UUID généré si absent).

Types de « part »

Part texte
{ "text": "Quel temps fait-il à Sceaux ?" }
Part fichier (image)
{
  "file": {
    "name": "photo.png",
    "mimeType": "image/png",
    "bytes": "<base64 sans préfixe data:>"
  }
}

Exemples concrets

a) Appel simple en curl

curl -X POST http://localhost:8001/ \
  -H "Content-Type: application/json" \
        -H "x-m365-id-token: $M365_ID_TOKEN" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "SendMessage",
    "params": {
      "message": {
        "role": "ROLE_USER",
        "parts": [ { "text": "Bonjour, que sais-tu faire ?" } ]
      }
    }
  }'

b) Appel en JavaScript (fetch)

const res = await fetch("http://localhost:8001/", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-m365-id-token": idToken,
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "req-1",
    method: "SendMessage",
    params: {
      message: {
        role: "ROLE_USER",
        parts: [{ text: "Météo à Sceaux demain ?" }],
        contextId: "conv-42", // réutiliser pour la mémoire
      },
    },
  }),
});
const data = await res.json();
console.log(data.result.status.message.parts[0].text);

c) Conserver le contexte (mémoire de conversation)

Renvoyez le même contextId à chaque tour : l'agent retrouve l'historique et les compteurs de consommation associés à cet échange.

Joindre ou modifier une image

Ajoutez une part fichier à côté de la part texte. C'est le modèle qui décide s'il s'agit d'analyser l'image ou de la modifier via le générateur d'image — guidé par votre instruction texte.

{
  "jsonrpc": "2.0", "id": 7, "method": "SendMessage",
  "params": {
    "message": {
      "role": "ROLE_USER",
      "parts": [
        { "text": "Mets cette photo en noir et blanc" },
        { "file": { "mimeType": "image/png", "bytes": "iVBORw0KGgo…" } }
      ]
    }
  }
}
Conseil Réduisez l'image avant l'envoi (le portail la ramène à 1024 px max en PNG). Les octets sont en base64 brut, sans le préfixe data:image/png;base64,.

Streaming (Server-Sent Events)

Avec SendStreamingMessage, la réponse n'est pas un JSON unique mais un flux d'événements text/event-stream. Chaque ligne data: contient une enveloppe JSON-RPC : d'abord la tâche (task), puis l'artefact (artifact, marqué lastChunk), puis le statut final (status = COMPLETED).

curl -N -X POST http://localhost:8001/ \
  -H "Content-Type: application/json" \
        -H "x-m365-id-token: $M365_ID_TOKEN" \
  -d '{ "jsonrpc":"2.0", "id":2, "method":"SendStreamingMessage",
        "params":{ "message":{ "role":"ROLE_USER",
          "parts":[{"text":"Raconte une courte histoire"}] } } }'

// Flux reçu :
data: {"jsonrpc":"2.0","id":2,"result":{"task":{…,"status":{"state":"TASK_STATE_WORKING"}}}}
data: {"jsonrpc":"2.0","id":2,"result":{"artifactUpdate":{…,"lastChunk":true}}}
data: {"jsonrpc":"2.0","id":2,"result":{"statusUpdate":{…,"state":"TASK_STATE_COMPLETED"}}}

Structure de la réponse

Pour SendMessage, le champ result est une tâche (Task). La réponse en langage naturel se trouve dans result.status.message.parts[0].text (et aussi dans result.artifacts).

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "<taskId>",
    "contextId": "conv-42",
    "status": {
      "state": "TASK_STATE_COMPLETED",
      "message": { "role": "ROLE_AGENT",
        "parts": [{ "text": "Voici la réponse…" }] },
      "timestamp": "2026-06-15T…Z"
    },
    "artifacts": [ { "parts": [{ "text": "Voici la réponse…" }] } ],
    "metadata": {
      "usage": { "prompt_tokens": 120, "completion_tokens": 60, "total_tokens": 180 },
      "cost":  { "prompt": 0.0001, "completion": 0.0002,
                 "image": 0, "total": 0.0003, "currency": "EUR" },
      "imageUsage": { "input_tokens": 0, "output_tokens": 0, "mode": "generate" },
      "tools": [ { "name": "get_weather" } ]
    }
  }
}
Champ de metadata Contenu
usage Tokens du modèle texte (entrée / sortie / total).
cost Coût détaillé du tour : texte (prompt + completion), image, total et currency.
imageUsage Présent si une image a été produite : tokens image et mode = "generate" (création) ou "edit" (modification d'une image jointe).
tools Liste des skills appelés pendant le tour.
Suivre la consommation L'agent garde aussi ces compteurs en mémoire côté serveur. Demandez-lui simplement « combien ai-je consommé ? » et le skill get_usage_totals restitue le cumul (tokens + coût) de la conversation.

Codes d'erreur

En cas de problème, le champ result est remplacé par error: { code, message }.

Code Signification
-32010 Non authentifié — x-m365-id-token manquant ou invalide (HTTP 401).
-32600 Requête invalide — jsonrpc ≠ "2.0" ou id absent.
-32601 Méthode inconnue (method non supporté).
-32602 Paramètres invalides — par ex. message.parts manquant.
-32603 Erreur interne du serveur.
-32001 Tâche introuvable (GetTask).
-32002 Tâche non annulable (CancelTask).
Cas particulier : authentification déléguée Si un skill a besoin d'un jeton utilisateur expiré, l'agent ne renvoie pas d'erreur brute : il répond une tâche normale avec un message explicatif et un indicateur authRequired dans metadata.

Variables d'environnement utiles

Variable Rôle
AGENT_PORT Port de l'agent autonome (défaut 8001).
PORT Port du portail (défaut 8000).
HOST Interface d'écoute (défaut 127.0.0.1) ; 0.0.0.0 pour exposer sur le réseau.
M365_CLIENT_ID Identifiant public de l'application Entra ID dont les id_token sont acceptés par le serveur.
JSON_BODY_LIMIT Taille max du corps JSON (défaut 8mb).
TRUST_PROXY Réglage Express trust proxy (défaut loopback).