Channels
Ajouté dans : @mastra/core@1.22.0
Channels connecte les agents aux plateformes de messagerie. Configurez-les via la propriété channels du constructeur Agent. L’objet transmis est un ChannelConfig. Consultez la présentation de Channels pour les concepts et les instructions de configuration des plateformes.
Exemple d’utilisationLien direct vers Exemple d’utilisation
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
import { createDiscordAdapter } from '@chat-adapter/discord'
export const supportAgent = new Agent({
id: 'support-agent',
name: 'Support Agent',
instructions: 'You are a helpful support assistant.',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
discord: createDiscordAdapter(),
},
},
})
ParamètresLien direct vers Paramètres
La propriété channels accepte un objet ChannelConfig avec les champs suivants :
adapters:
slack, discord). Transmettez directement un Adapter pour les valeurs par défaut, ou un objet ChannelAdapterConfig pour personnaliser les options par adaptateur.handlers?:
inlineMedia?:
inlineLinks?:
tools?:
getTools() renvoie les outils propres au canal (add_reaction, remove_reaction). Définissez sur false pour les modèles qui ne prennent pas en charge l’appel de fonctions. Les outils de canal ne sont jamais ajoutés automatiquement à l’agent ; transmettez-les explicitement avec tools: { ...channels.getTools() }.state?:
MastraStateAdapter, soutenu par le stockage de l’instance Mastra. Channels exige que le stockage soit configuré.userName?:
name de l’agent, ou 'Mastra' si aucun nom n’est défini.threadContext?:
maxMessages contrôle le nombre de messages récents de la plateforme récupérés lors de la première mention (définissez sur 0 pour désactiver ; s’applique uniquement aux threads non-DM). addSystemMessage: false ignore le message système intégré qui indique à l’agent de quel canal ou de quelle plateforme provient une requête.chatOptions?:
dedupeTtlMs, fallbackStreamingPlaceholderText, lockScope et messageHistory.resolveResourceId?:
resourceId possède la mémoire au niveau de la ressource pour un thread de canal, indépendamment de l’expéditeur du message. S’exécute uniquement à la création d’un thread ; les threads réutilisés conservent leur propriétaire enregistré et n’appellent jamais le hook. Renvoyez ctx.defaultResourceId (${platform}:${message.author.userId}) pour conserver le comportement intégré.resolveThreadId?:
resolveResourceId, avec le propriétaire résolu dans le contexte, et uniquement à la création d’un thread ; les threads réutilisés conservent leur identifiant enregistré et n’appellent jamais le hook. L’identifiant renvoyé doit être unique dans le magasin de mémoire ; en cas de collision, un identifiant généré est utilisé. Renvoyez ctx.defaultThreadId (un UUID aléatoire) pour conserver le comportement intégré.waitUntil?:
waitUntil de la plateforme. Requise sur Vercel pour que les exécutions d’agents en arrière-plan continuent après que le webhook a renvoyé 200. Sur Vercel, transmettez waitUntil depuis @vercel/functions. Cloudflare Workers et Netlify Functions sont détectés automatiquement depuis le contexte de la requête. AWS Lambda n’a pas besoin de waitUntil, car il attend naturellement que la boucle d’événements se vide.resolveWaitUntil?:
waitUntil se trouve dans le contexte de requête Hono, mais n’est pas pris en charge par l’assistant intégré. Ordre de résolution : waitUntil seul → resolveWaitUntil(c) → valeur par défaut (Cloudflare Workers, Netlify).Options par adaptateurLien direct vers Options par adaptateur
Enveloppez un adaptateur dans un objet ChannelAdapterConfig pour définir des options par adaptateur :
import { Agent } from '@mastra/core/agent'
import { createDiscordAdapter } from '@chat-adapter/discord'
import { createSlackAdapter } from '@chat-adapter/slack'
const agent = new Agent({
id: 'example',
name: 'Example',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
discord: {
adapter: createDiscordAdapter(),
toolDisplay: 'text',
cors: {
origin: ['https://customer-saas.example'],
credentials: true,
},
gateway: false,
},
slack: createSlackAdapter(), // Plain adapter uses defaults
},
},
})
adapter:
gateway?:
false pour les déploiements serverless qui n’ont besoin que d’interactions basées sur des webhooks.cards?:
toolDisplay. Lorsque toolDisplay n’est pas défini, cards: true correspond à toolDisplay: "cards" et cards: false à toolDisplay: "text". Les IDE affichent ce champ barré ; le comportement à l’exécution est préservé.cors?:
formatError?:
formatToolCall?:
toolDisplay (sous forme de fonction). Lorsqu’il est défini, s’exécute comme un ToolDisplayFn qui ne se déclenche que sur les événements result/error ; les événements running et approval ne produisent aucun rendu. Mutuellement exclusif avec toolDisplay au niveau du type.streaming?:
true par défaut ; les autres adaptateurs utilisent false.textFormat?:
'markdown' (valeur par défaut) publie les réponses en Markdown : les adaptateurs avec un rendu Markdown natif (Slack) l’affichent directement, les autres le convertissent au format de leur plateforme. 'plain' publie les réponses sous forme de texte brut littéral et rétablit le comportement antérieur à Markdown pour les agents invités à produire un dialecte de plateforme tel que Slack mrkdwn. S’applique uniquement au texte de réponse final ; les cartes d’outils, messages d’erreur et avis de déclenchement ne sont pas affectés. Le streaming natif est toujours en Markdown, quel que soit ce paramètre.toolDisplay?:
"cards" publie des cartes Block Kit riches par outil pour l’exécution et le résultat. "text" publie le même cycle de vie en texte brut (sans Block Kit). "timeline" et "grouped" diffusent l’état des outils sous forme de blocs task_update en ligne (nécessite streaming: true ; Slack uniquement pour le moment — les autres adaptateurs peuvent afficher un espace réservé). "hidden" exécute les outils silencieusement. Transmettez une fonction pour afficher vous-même les événements d’outils ; renvoyez { kind: "post", message } pour une publication/modification distincte, { kind: "stream", chunk } pour envoyer un élément au widget de streaming, ou undefined pour ignorer le rendu de cet événement. Ajoutez openIfEmpty: false à un résultat de streaming lorsque son bloc ne doit s’appliquer qu’à une session de streaming active. Les invites d’approbation/de refus s’affichent toujours comme une carte distincte, quel que soit le mode.typingStatus?:
true utilise les valeurs par défaut intégrées (is typing… pour le texte, is calling {tool}… pour un appel d’outil, is waiting for approval… pour une approbation d’appel d’outil). false supprime entièrement l’indicateur de saisie — utile lorsqu’un widget de streaming en direct (par ex. toolDisplay: "grouped" dans Slack) indique déjà la progression. Transmettez une fonction pour définir un texte d’état personnalisé par bloc ; renvoyez une chaîne pour définir l’état, ou false/null/undefined pour le laisser inchangé. Composez avec defaultTypingStatus (exporté depuis @mastra/core/channels) pour revenir aux valeurs par défaut pour les blocs que vous ne gérez pas.Modes d’affichage des outilsLien direct vers Modes d’affichage des outils
toolDisplay contrôle le rendu des appels d’outils dans le chat. La valeur par défaut, 'cards',
publie une carte « Running… » par outil et la modifie avec le résultat, conformément
au comportement des versions précédentes. 'text' suit le même cycle de vie, mais sans
Block Kit enrichi ; il est utile pour les plateformes qui n’affichent pas bien les cartes.
'timeline' et 'grouped' diffusent l’état des outils sous forme de blocs task_update en ligne
à côté du texte de l’agent. Ces modes nécessitent streaming: true et reposent sur
l’adaptateur de chat pour afficher les blocs. Slack prend les deux en charge nativement ; les autres
adaptateurs peuvent afficher un espace réservé jusqu’à leur prise en charge. Si streaming est
désactivé, le canal journalise un avertissement unique et revient à 'cards'.
'hidden' exécute les outils silencieusement. Seul l’état de saisie indique le travail
en cours.
Transmettez une fonction à toolDisplay pour un rendu entièrement personnalisé. La fonction
reçoit un ToolDisplayEvent (running / result / error / approval)
et un ToolDisplayContext ({ mode, platform }) ; renvoyez { kind: 'post', message } pour une publication/modification distincte, { kind: 'stream', chunk } pour envoyer un élément
au widget de streaming actif, ou undefined pour ignorer le rendu de cet événement.
Par défaut, un résultat de streaming ouvre une session de streaming si aucune n’est active. Définissez
openIfEmpty: false lorsque le bloc s’applique uniquement à une session existante. Mastra
ignore le bloc si aucune session n’est active. Les canaux statiques ignorent cette option
et conservent leur comportement de repli existant en texte brut.
toolDisplay: event => {
if (event.kind !== 'running') return undefined
return {
kind: 'stream',
chunk: {
type: 'task_update',
id: event.toolCallId,
title: event.displayName,
status: 'in_progress',
},
openIfEmpty: false,
}
}
Les invites d’approbation/de refus (requireApproval) s’affichent toujours comme une carte
distincte, quel que soit le mode, car les entrées de tâche en ligne ne peuvent pas contenir de
boutons interactifs.
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
const agent = new Agent({
id: 'streaming-agent',
name: 'Streaming Agent',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: {
adapter: createSlackAdapter(),
streaming: true, // already the Slack default
toolDisplay: 'timeline',
},
},
},
})
État de saisie personnaliséLien direct vers État de saisie personnalisé
Transmettez une fonction à typingStatus pour personnaliser le texte d’état. La fonction est
appelée une fois par bloc de streaming ; renvoyez une chaîne pour définir l’état, ou false /
null / undefined pour laisser l’état actuel inchangé. Les valeurs de retour sont
dédupliquées afin que la plateforme ne reçoive un appel que lorsque l’état change.
defaultTypingStatus est exporté depuis @mastra/core/channels afin que vous puissiez
revenir aux valeurs par défaut intégrées pour les blocs que vous ne gérez pas.
import { Agent } from '@mastra/core/agent'
import { defaultTypingStatus } from '@mastra/core/channels'
import { createDiscordAdapter } from '@chat-adapter/discord'
const agent = new Agent({
id: 'custom-typing-agent',
name: 'Custom Typing Agent',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
discord: {
adapter: createDiscordAdapter(),
typingStatus: (chunk, ctx) => {
if (chunk.type === 'tool-call' && chunk.payload.toolName === 'searchDocs') {
return 'is searching docs…'
}
return defaultTypingStatus(chunk, ctx)
},
},
},
},
})
GestionnairesLien direct vers Gestionnaires
Remplacez les gestionnaires d’événements intégrés. Chaque gestionnaire peut être :
- Omis : utilise le gestionnaire Mastra par défaut (transmet le message à l’agent et publie la réponse).
false: désactive entièrement le gestionnaire.- Une fonction
(thread, message, defaultHandler) => Promise<void>: encapsule ou remplace le gestionnaire par défaut.
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
const agent = new Agent({
id: 'custom-handler-agent',
name: 'Custom Handler Agent',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
handlers: {
onMention: async (thread, message, defaultHandler) => {
console.log('Received mention:', message.text)
await defaultHandler(thread, message)
},
onDirectMessage: false,
},
},
})
onDirectMessage?:
onMention?:
onSubscribedMessage?:
Signature de la fonction ChannelHandler :
type ChannelHandler = (
thread: Thread,
message: Message,
defaultHandler: (thread: Thread, message: Message) => Promise<void>,
ctx: ChannelHandlerContext,
) => Promise<void>
type ChannelHandlerContext = {
mastra?: Mastra
requestContext: RequestContext
}
ctx.mastra est l’instance mastra résolue ; un gestionnaire peut donc accéder au stockage ou à d’autres primitives enregistrées sans qu’un accesseur externe lui soit transmis :
onDirectMessage: async (thread, message, defaultHandler, ctx) => {
const store = await ctx.mastra?.getStorage()?.getStore('memory')
await defaultHandler(thread, message)
}
ctx.requestContext est le RequestContext de l’exécution que ce message est sur le point de démarrer, créé à neuf pour chaque message. Écrivez-y avant d’appeler defaultHandler ; la valeur parvient à l’exécution avec les entrées de canal ajoutées ensuite par Mastra :
onDirectMessage: async (thread, message, defaultHandler, ctx) => {
ctx.requestContext.set('locale', 'en-GB')
await defaultHandler(thread, message)
}
De cette manière, tout ce que l’exécution lit dans son contexte de requête peut être déterminé par message, comme l’utilisateur auquel correspond un expéditeur de plateforme.
Résolution de l’identifiant de ressourceLien direct vers Résolution de l’identifiant de ressource
Par défaut, le resourceId de mémoire d’un thread de canal est ${platform}:${message.author.userId}. L’expéditeur possède la mémoire, délimitée par plateforme. Pour les applications avec une identité partagée, telle que l’authentification unique (SSO), cela sépare la mémoire : le même utilisateur obtient feishu:user_123 dans un DM Feishu, mais user_123 sur le Web.
Transmettez resolveResourceId pour décider de la propriété de la mémoire indépendamment de l’expéditeur. Il s’exécute uniquement lors de la création d’un thread. Les threads réutilisés conservent leur resourceId enregistré et n’appellent jamais le hook ; les conversations existantes ne dépendent donc pas de la disponibilité du résolveur. Renvoyez ctx.defaultResourceId pour revenir au comportement intégré.
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
const agent = new Agent({
id: 'sso-agent',
name: 'SSO Agent',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
resolveResourceId: async ({ thread, message }) => {
// DM: share resource-level memory with the web app by using the bare SSO id
if (thread.isDM) {
return await resolveSsoUserId(message)
}
// Group chat: the conversation owns the memory; the sender stays the actor
return thread.channelId
},
},
})
ResolveResourceIdContext transmis à la fonction :
platform:
slack, discord).thread:
thread.isDM pour distinguer les DM des threads de groupe ou de canal.message:
message.author.userId est l’acteur ou l’expéditeur, pas nécessairement le propriétaire de la mémoire.defaultResourceId:
${platform}:${message.author.userId}). Renvoyez-la pour conserver le comportement actuel.Résolution de l’identifiant de threadLien direct vers Résolution de l’identifiant de thread
Par défaut, un nouveau thread de canal reçoit un UUID aléatoire comme identifiant de thread Mastra interne. Transmettez resolveThreadId pour choisir vous-même l’identifiant : par exemple, donnez au thread le même identifiant que la session à laquelle il appartient, conformément à la façon dont votre application nomme les threads qu’elle crée elle-même.
Le hook s’exécute après resolveResourceId, ainsi le propriétaire résolu est disponible dans le contexte. Comme resolveResourceId, il s’exécute uniquement à la création d’un thread : les threads réutilisés conservent leur identifiant enregistré et n’appellent jamais le hook. L’identifiant renvoyé doit être unique dans le magasin de mémoire. S’il appartient déjà à un thread existant, Mastra journalise un avertissement et utilise un identifiant généré afin que le thread existant ne soit jamais écrasé. Renvoyez ctx.defaultThreadId pour conserver le comportement intégré.
import { Agent } from '@mastra/core/agent'
import { createSlackAdapter } from '@chat-adapter/slack'
const agent = new Agent({
id: 'session-agent',
name: 'Session Agent',
instructions: '...',
model: 'openai/gpt-5.6-sol',
channels: {
adapters: {
slack: createSlackAdapter(),
},
// Owner: a session id resolved from the sender's linked account
resolveResourceId: async ctx => resolveSessionId(ctx),
// Thread id: align with the session id so app URLs that address
// threads by session id resolve channel-created threads too
resolveThreadId: ({ resourceId, defaultThreadId }) => {
return isSessionId(resourceId) ? resourceId : defaultThreadId
},
},
})
ResolveThreadIdContext transmis à la fonction :
platform:
slack, discord).thread:
thread.isDM pour distinguer les DM des threads de groupe ou de canal.message:
resourceId:
resourceId de mémoire résolu auquel appartiendra le nouveau thread (après resolveResourceId).defaultThreadId:
Médias intégrésLien direct vers Médias intégrés
Contrôle les types de pièces jointes (images, vidéo, PDF, etc.) envoyés au modèle sous forme de parties de fichier. Les types qui ne correspondent pas sont décrits sous forme de résumés textuels, afin que l’agent connaisse le fichier sans faire échouer les modèles qui refusent les types non pris en charge.
La valeur par défaut (['image/png', 'image/jpeg', 'image/webp', 'application/pdf']) correspond aux formats pris en charge par les principaux modèles de vision. Remplacez inlineMedia pour étendre la liste (par ex. ['image/*', 'audio/*']) ou la remplacer entièrement par une fonction prédicat.
Motifs glob pris en charge :
| Motif | Correspondances |
|---|---|
image/* | Tous les types d’image (image/png, image/jpeg, etc.) |
video/* | Tous les types de vidéo |
* ou */* | Tous les types |
application/pdf | Correspondance exacte du type |
Pour les plateformes avec des CDN privés (par ex. Slack), les pièces jointes sont récupérées avec des identifiants authentifiés depuis le Chat SDK. Pour les plateformes avec des CDN publics (par ex. Discord), l’URL est transmise directement au modèle.
Liens intégrésLien direct vers Liens intégrés
Convertit les URL trouvées dans le texte des messages en parties de fichier afin que le modèle puisse traiter le contenu lié au lieu de voir le texte brut de l’URL. Chaque entrée peut être une chaîne (motif de domaine) ou un objet avec un type MIME forcé.
Les entrées de chaîne correspondent à un domaine et effectuent une requête HEAD pour détecter le Content-Type. Le type résolu est vérifié par rapport à inlineMedia et seuls les types correspondants deviennent des parties de fichier.
Les entrées d’objet correspondent à un domaine et forcent un type MIME donné, sans effectuer la requête HEAD ni la vérification inlineMedia. Cela est utile pour des sites tels que YouTube, où une requête HEAD renvoie text/html, mais le modèle traite l’URL comme un contenu vidéo.
type InlineLinkEntry =
| string // Domain pattern (HEAD determines mime type)
| { match: string; mimeType: string } // Domain + forced mime type (skips HEAD)
Ressources associéesLien direct vers Ressources associées
- Présentation de Channels : concepts, démarrage rapide et configuration des plateformes
- Classe Agent : paramètres et méthodes du constructeur
- Adaptateurs Chat SDK : configuration des adaptateurs et des plateformes