跳到主要内容

Channel

新增于: @mastra/core@1.22.0

Channels 将 Agent 连接到消息平台。请通过 Agent 构造函数的 channels 属性进行配置;传入的对象类型为 ChannelConfig。有关概念和平台设置说明,请参阅 Channels 概览

使用示例
使用示例的直接链接

src/mastra/agents/support-agent.ts
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(),
},
},
})

参数
参数的直接链接

channels 属性接受包含以下字段的 ChannelConfig 对象:

adapters:

Record<string, Adapter | ChannelAdapterConfig>
以名称(例如 slackdiscord)为键的平台 adapter。直接传入 Adapter 可使用默认值;传入 ChannelAdapterConfig 对象可自定义各 adapter 的选项。

handlers?:

ChannelHandlers
覆盖私信、提及和已订阅 thread 的默认消息 handler。

inlineMedia?:

string[] | ((mimeType: string) => boolean)
= ['image/png', 'image/jpeg', 'image/webp', 'application/pdf']
控制哪些附件类型作为文件 part 发送给模型。不匹配的类型会以文本摘要描述。接受 MIME 类型 glob 数组或 predicate 函数。默认值匹配主流视觉模型支持的格式。

tools?:

boolean
= true
getTools() 是否返回 Channel 专属 Tool(add_reactionremove_reaction)。对于不支持 function calling 的模型,请设为 false。Channel Tool 绝不会自动添加到 Agent;请通过 tools: { ...channels.getTools() } 显式传入。

state?:

StateAdapter
= MastraStateAdapter(来自 Mastra storage)
用于订阅和去重的状态 adapter。默认为由 Mastra 实例存储支持的 MastraStateAdapter。Channels 要求配置 storage。

userName?:

string
= Agent 的 `name`
平台消息中显示的机器人名称。默认为 Agent 的 name;未设置名称时为 'Mastra'

threadContext?:

{ maxMessages?: number; addSystemMessage?: boolean }
= { maxMessages: 10, addSystemMessage: true }
Agent 获取当前 thread 上下文的方式。maxMessages 控制首次被提及时获取多少条近期平台消息(设为 0 可禁用;仅适用于非私信 thread)。addSystemMessage: false 会跳过用于告知 Agent 请求来自哪个 Channel/平台的内置 system message。

chatOptions?:

Omit<ChatConfig, 'adapters' | 'state' | 'userName'>
直接传给 Chat SDK 的其他选项。用于 dedupeTtlMsfallbackStreamingPlaceholderTextlockScopemessageHistory 等高级配置。

resolveResourceId?:

(ctx: ResolveResourceIdContext) => string | Promise<string>
决定哪个 resourceId 拥有 Channel thread 的资源级 Memory,与消息发送者分开处理。仅在创建新 thread 时运行;复用的 thread 保留已存储的所有者,且绝不会调用该 hook。返回 ctx.defaultResourceId${platform}:${message.author.userId})可保留内置行为。

resolveThreadId?:

(ctx: ResolveThreadIdContext) => string | Promise<string>
决定 Channel thread 的内部 Mastra thread ID。在 resolveResourceId 之后运行,此时上下文中已有解析后的所有者;并且仅在创建新 thread 时运行。复用的 thread 保留已存储的 ID,且绝不会调用该 hook。返回的 ID 必须在 memory store 中唯一;发生冲突时会改用生成的 ID。返回 ctx.defaultThreadId(随机 UUID)可保留内置行为。

waitUntil?:

(promise: Promise<unknown>) => void
平台的 waitUntil 函数。在 Vercel 上必须提供,以便 webhook 返回 200 后后台 Agent run 仍能继续。在 Vercel 上请传入 @vercel/functionswaitUntil。Cloudflare Workers 和 Netlify Functions 会从 request context 自动检测。AWS Lambda 无需 waitUntil,因为它会自然等待事件循环清空。

resolveWaitUntil?:

(c: Context) => ((promise: Promise<unknown>) => void) | undefined
适用于 waitUntil 位于 Hono request context 中、但内置 helper 未覆盖的 runtime 的 resolver。解析顺序:裸 waitUntilresolveWaitUntil(c) → 默认值(Cloudflare Workers、Netlify)。

各 adapter 选项
各 adapter 选项的直接链接

将 adapter 包装在 ChannelAdapterConfig 对象中,以设置各 adapter 的选项:

src/mastra/agents/example.ts
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:

Adapter
此平台的 Chat SDK adapter 实例。

gateway?:

boolean
= true
启动持久的 Gateway WebSocket listener,以接收私信、@提及和 reaction。对于只需基于 webhook 交互的 serverless 部署,请设为 false

cards?:

boolean
**已弃用** — 请改用 toolDisplay。未设置 toolDisplay 时,cards: true 映射到 toolDisplay: "cards"cards: false 映射到 toolDisplay: "text"。IDE 会以删除线标记该字段,但运行时行为保持不变。

cors?:

CorsOptions
此 adapter webhook 路由的 CORS 配置。适用于需要跨域凭据、基于浏览器的 Channel adapter。

formatError?:

(error: Error) => PostableMessage
= "❌ Error: <error.message>"
覆盖聊天中错误的渲染方式。返回用户友好的消息,避免暴露原始错误。

formatToolCall?:

(args: { toolName: string; args: unknown; result: unknown; isError: boolean }) => PostableMessage | null
**已弃用** — 请改用函数形式的 toolDisplay。设置后,它会作为 ToolDisplayFn 运行,并且仅在 result/error 事件中触发;runningapproval 事件不会渲染。类型层面不能与 toolDisplay 同时使用。

streaming?:

boolean | { updateIntervalMs?: number }
= false (true for Slack)
在 Agent 生成文本增量时将其流式传输到 Channel,而不是缓冲后在每个步骤发布一次。要求底层 adapter 支持发布并编辑的流式传输。Slack 默认为 true,其他 adapter 默认为 false

textFormat?:

'markdown' | 'plain'
= 'markdown'
Agent 最终回复文本的格式。'markdown'(默认值)以 Markdown 发布回复:原生支持 Markdown 渲染的 adapter(如 Slack)会直接渲染,其他 adapter 则转换为平台格式。'plain' 以纯文本发布回复,可让被指示输出 Slack mrkdwn 等平台格式的 Agent 恢复到引入 Markdown 前的行为。此设置仅影响最终回复文本,不影响 Tool 卡片、错误消息和 tripwire 通知。无论此设置为何值,原生 streaming 始终使用 Markdown。

toolDisplay?:

'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn
= 'cards' ('grouped' for Slack)
控制 Tool 调用在 Channel 中的渲染方式。"cards" 使用富 Block Kit 为每个 Tool 发布运行中/结果卡片。"text" 以纯文本发布相同的生命周期(不使用 Block Kit)。"timeline""grouped" 将 Tool 状态作为内联 task_update chunk 进行 streaming(要求 streaming: true;目前仅支持 Slack,其他 adapter 可能渲染占位符)。"hidden" 静默执行 Tool。也可以传入函数自行渲染 Tool 事件:返回 { kind: "post", message } 以单独发布/编辑消息,返回 { kind: "stream", chunk } 将内容推送到 streaming widget,返回 undefined 跳过该事件的渲染。如果 chunk 只应应用于活动的 streaming Session,请在 stream 结果中添加 openIfEmpty: false。无论采用哪种模式,批准/拒绝 prompt 始终渲染为单独的卡片。

typingStatus?:

boolean | ((chunk: AgentChunkType, ctx: TypingStatusContext) => string | false | null | undefined | void)
= true
控制平台的输入状态指示器。true 使用内置默认值(文本时显示 is typing…,Tool 调用时显示 is calling {tool}…,Tool 调用待批准时显示 is waiting for approval…)。false 完全隐藏输入状态,适用于实时 streaming widget(例如 Slack 中的 toolDisplay: "grouped")已能体现处理进度的情况。可以传入函数,为每个 chunk 设置自定义状态文案;返回字符串以设置状态,返回 false/null/undefined 则保持不变。可与 defaultTypingStatus(从 @mastra/core/channels 导出)组合使用,让未处理的 chunk fallback 到默认状态。

Tool 显示模式
Tool 显示模式的直接链接

toolDisplay 控制 Tool 调用在聊天中的渲染方式。默认的 'cards' 会为每个 Tool 发布一张“运行中……”卡片,并使用结果编辑卡片,与 旧版本行为一致。'text' 具有相同生命周期,但不使用富 Block Kit,适合无法良好渲染卡片的平台。

'timeline''grouped' 会将 Tool 状态作为内联 task_update chunk,与 Agent 文本一起进行 stream。这些模式要求设置 streaming: true,并依赖 chat adapter 渲染 chunk。Slack 原生支持这两种模式;其他 adapter 在增加支持前可能会渲染占位符。如果禁用 streaming,Channel 会记录一次警告并回退到 'cards'

'hidden' 会静默执行 Tool。只有输入状态会指示工作 正在进行。

toolDisplay 传入函数可完全自定义渲染。该函数接收 ToolDisplayEventrunning / result / error / approval)和 ToolDisplayContext{ mode, platform });返回 { kind: 'post', message } 可单独发布或编辑消息,返回 { kind: 'stream', chunk } 可将内容推送到活跃的 streaming widget,返回 undefined 则跳过该事件的渲染。

默认情况下,如果没有活跃 Session,stream 结果会打开一个 streaming Session。如果 chunk 仅适用于现有 Session,请设置 openIfEmpty: false。没有活跃 Session 时,Mastra 会跳过该 chunk。静态 Channel 会忽略此选项,并保留现有的纯文本回退行为。

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,
}
}

无论采用哪种模式,批准/拒绝 Prompt(requireApproval)始终渲染为单独的卡片,因为内联任务条目无法包含交互式按钮。

src/mastra/agents/streaming.ts
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',
},
},
},
})

自定义输入状态
自定义输入状态的直接链接

typingStatus 传入函数可自定义状态文本。该函数会 对每个 stream chunk 调用一次;返回字符串可设置状态,返回 false / null / undefined 则保持当前状态不变。返回值会 去重,因此平台只会在状态变化时收到调用。

defaultTypingStatus@mastra/core/channels 导出,因此对于未处理的 chunk,可以回退到内置默认值。

src/mastra/agents/custom-typing.ts
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)
},
},
},
},
})

Handler
Handler的直接链接

覆盖内置事件 handler。每个 handler 可以是:

  • 省略:使用 Mastra 默认 handler(将消息路由到 Agent 并发布响应)
  • false:完全禁用 handler
  • 函数 (thread, message, defaultHandler) => Promise<void>:包装或替换默认 handler
src/mastra/agents/custom-handlers.ts
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?:

ChannelHandler | false
机器人收到私信时调用。

onMention?:

ChannelHandler | false
机器人在 Channel 或 thread 中被 @提及时调用。

onSubscribedMessage?:

ChannelHandler | false
Agent 已订阅的 thread 中出现消息时调用。

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 是解析后的 mastra 实例,因此无需传入外部 accessor,handler 即可访问 storage 或其他已注册 primitive:

onDirectMessage: async (thread, message, defaultHandler, ctx) => {
const store = await ctx.mastra?.getStorage()?.getStore('memory')
await defaultHandler(thread, message)
}

ctx.requestContext 是此消息即将启动的 run 所用的 RequestContext,每条消息都会重新构建。在调用 defaultHandler 前写入它,该值就会与 Mastra 随后添加的 Channel 条目一起传入 run:

onDirectMessage: async (thread, message, defaultHandler, ctx) => {
ctx.requestContext.set('locale', 'en-GB')
await defaultHandler(thread, message)
}

通过这种方式,可以为每条消息决定 run 从其 request context 读取的任何内容,例如平台发送者映射到哪个用户。

Resource ID 解析
Resource ID 解析的直接链接

默认情况下,Channel thread 的 Memory resourceId${platform}:${message.author.userId}。Memory 归发送者所有,并按平台划分。对于使用共享身份(如单点登录 SSO)的应用,这会拆分 Memory:同一用户在飞书私信中的 ID 是 feishu:user_123,而在 Web 端则是 user_123

传入 resolveResourceId 可独立于发送者决定 Memory 归属。它仅在创建新 thread 时运行。复用的 thread 会保留已存储的 resourceId,且绝不会调用此 hook,因此现有对话不依赖 resolver 是否可用。返回 ctx.defaultResourceId 可回退到内置行为。

src/mastra/agents/sso-agent.ts
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

platform:

string
平台名称(例如 slackdiscord)。

thread:

Thread
消息到达的 Channel thread。使用 thread.isDM 区分私信与群组/Channel thread。

message:

Message
传入的消息。message.author.userId 是 actor/发送者,不一定是 Memory 所有者。

defaultResourceId:

string
内置默认值(${platform}:${message.author.userId})。返回此值可保留当前行为。

Thread ID 解析
Thread ID 解析的直接链接

默认情况下,新 Channel thread 会获得一个随机 UUID 作为其内部 Mastra thread ID。传入 resolveThreadId 可自行选择 ID,例如让 thread 使用其所属 Session 的同一 ID,以匹配应用为自行创建的 thread 所采用的命名方式。

该 hook 在 resolveResourceId 之后运行,因此 context 中可以访问解析后的所有者。与 resolveResourceId 一样,它仅在创建新 thread 时运行:复用的 thread 会保留已存储的 ID,且绝不会调用此 hook。返回的 ID 在整个 Memory Store 中必须唯一。如果它已属于现有 thread,Mastra 会记录警告并改用生成的 ID,从而确保不会覆盖现有 thread。返回 ctx.defaultThreadId 可保留内置行为。

src/mastra/agents/session-agent.ts
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

platform:

string
平台名称(例如 slackdiscord)。

thread:

Thread
消息到达的 Channel thread。使用 thread.isDM 区分私信与群组/Channel thread。

message:

Message
传入的消息。

resourceId:

string
新 thread 将归属的已解析 memory resourceId(在 resolveResourceId 之后)。

defaultThreadId:

string
内置默认值(随机 UUID)。返回此值可保留当前行为。

内联媒体
内联媒体的直接链接

控制将哪些附件类型(图片、视频、PDF 等)作为文件 part 发送给模型。不匹配的类型会以文本摘要描述,使 Agent 能了解文件,同时避免不支持该类型的模型崩溃。

默认值(['image/png', 'image/jpeg', 'image/webp', 'application/pdf'])匹配主流视觉模型支持的格式。可覆盖 inlineMedia 来扩展列表(例如 ['image/*', 'audio/*']),或完全替换为 predicate 函数。

支持的 glob 模式:

模式匹配内容
image/*所有图片类型 (image/png, image/jpeg, etc.)
video/*所有视频类型
* or */*所有类型
application/pdf精确类型匹配

对于使用私有 CDN 的平台(例如 Slack),会使用 Chat SDK 的认证凭据获取附件。对于使用公共 CDN 的平台(例如 Discord),URL 会直接传给模型。

将消息文本中发现的 URL 提升为文件 part,使模型能够处理链接内容,而不是看到原始 URL 文本。每个条目可以是字符串(域名模式),也可以是指定 MIME 类型的对象。

字符串条目匹配域名,并发起 HEAD 请求检测 Content-Type。解析出的类型会与 inlineMedia 比对,只有匹配的类型才会成为文件 part。

对象条目匹配域名并强制指定 MIME 类型,从而跳过 HEAD 请求和 inlineMedia 检查。这适用于 YouTube 等网站:其 HEAD 请求返回 text/html,但模型会把 URL 当作视频内容处理。

type InlineLinkEntry =
| string // Domain pattern (HEAD determines mime type)
| { match: string; mimeType: string } // Domain + forced mime type (skips HEAD)