> Discover all available pages from the documentation index: https://mastra.zisheng.pro/llms.txt # Channel **新增于:** `@mastra/core@1.22.0` Channels 将 Agent 连接到消息平台。请通过 `Agent` 构造函数的 `channels` 属性进行配置;传入的对象类型为 `ChannelConfig`。有关概念和平台设置说明,请参阅 [Channels 概览](https://mastra.zisheng.pro/docs/capabilities/channels/overview)。 ## 使用示例 ```typescript 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`): 以名称(例如 slack、discord)为键的平台 adapter。直接传入 Adapter 可使用默认值;传入 ChannelAdapterConfig 对象可自定义各 adapter 的选项。 **handlers** (`ChannelHandlers`): 覆盖私信、提及和已订阅 thread 的默认消息 handler。 **inlineMedia** (`string[] | ((mimeType: string) => boolean)`): 控制哪些附件类型作为文件 part 发送给模型。不匹配的类型会以文本摘要描述。接受 MIME 类型 glob 数组或 predicate 函数。默认值匹配主流视觉模型支持的格式。 (Default: `['image/png', 'image/jpeg', 'image/webp', 'application/pdf']`) **inlineLinks** (`InlineLinkEntry[]`): 将消息文本中发现的 URL 提升为文件 part,使模型能够处理链接内容。每个条目匹配一个域名。默认禁用。 **tools** (`boolean`): getTools() 是否返回 Channel 专属 Tool(add\_reaction、remove\_reaction)。对于不支持 function calling 的模型,请设为 false。Channel Tool 绝不会自动添加到 Agent;请通过 tools: { ...channels.getTools() } 显式传入。 (Default: `true`) **state** (`StateAdapter`): 用于订阅和去重的状态 adapter。默认为由 Mastra 实例存储支持的 MastraStateAdapter。Channels 要求配置 storage。 (Default: `MastraStateAdapter(来自 Mastra storage)`) **userName** (`string`): 平台消息中显示的机器人名称。默认为 Agent 的 name;未设置名称时为 'Mastra'。 (Default: `` Agent 的 `name` ``) **threadContext** (`{ maxMessages?: number; addSystemMessage?: boolean }`): Agent 获取当前 thread 上下文的方式。maxMessages 控制首次被提及时获取多少条近期平台消息(设为 0 可禁用;仅适用于非私信 thread)。addSystemMessage: false 会跳过用于告知 Agent 请求来自哪个 Channel/平台的内置 system message。 (Default: `{ maxMessages: 10, addSystemMessage: true }`) **chatOptions** (`Omit`): 直接传给 Chat SDK 的其他选项。用于 dedupeTtlMs、fallbackStreamingPlaceholderText、lockScope 和 messageHistory 等高级配置。 **resolveResourceId** (`(ctx: ResolveResourceIdContext) => string | Promise`): 决定哪个 resourceId 拥有 Channel thread 的资源级 Memory,与消息发送者分开处理。仅在创建新 thread 时运行;复用的 thread 保留已存储的所有者,且绝不会调用该 hook。返回 ctx.defaultResourceId(${platform}:${message.author.userId})可保留内置行为。 **resolveThreadId** (`(ctx: ResolveThreadIdContext) => string | Promise`): 决定 Channel thread 的内部 Mastra thread ID。在 resolveResourceId 之后运行,此时上下文中已有解析后的所有者;并且仅在创建新 thread 时运行。复用的 thread 保留已存储的 ID,且绝不会调用该 hook。返回的 ID 必须在 memory store 中唯一;发生冲突时会改用生成的 ID。返回 ctx.defaultThreadId(随机 UUID)可保留内置行为。 **waitUntil** (`(promise: Promise) => void`): 平台的 waitUntil 函数。在 Vercel 上必须提供,以便 webhook 返回 200 后后台 Agent run 仍能继续。在 Vercel 上请传入 @vercel/functions 的 waitUntil。Cloudflare Workers 和 Netlify Functions 会从 request context 自动检测。AWS Lambda 无需 waitUntil,因为它会自然等待事件循环清空。 **resolveWaitUntil** (`(c: Context) => ((promise: Promise) => void) | undefined`): 适用于 waitUntil 位于 Hono request context 中、但内置 helper 未覆盖的 runtime 的 resolver。解析顺序:裸 waitUntil → resolveWaitUntil(c) → 默认值(Cloudflare Workers、Netlify)。 ## 各 adapter 选项 将 adapter 包装在 `ChannelAdapterConfig` 对象中,以设置各 adapter 的选项: ```typescript 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`): 启动持久的 Gateway WebSocket listener,以接收私信、@提及和 reaction。对于只需基于 webhook 交互的 serverless 部署,请设为 false。 (Default: `true`) **cards** (`boolean`): \*\*已弃用\*\* — 请改用 toolDisplay。未设置 toolDisplay 时,cards: true 映射到 toolDisplay: "cards",cards: false 映射到 toolDisplay: "text"。IDE 会以删除线标记该字段,但运行时行为保持不变。 **cors** (`CorsOptions`): 此 adapter webhook 路由的 CORS 配置。适用于需要跨域凭据、基于浏览器的 Channel adapter。 **formatError** (`(error: Error) => PostableMessage`): 覆盖聊天中错误的渲染方式。返回用户友好的消息,避免暴露原始错误。 (Default: `"❌ Error: "`) **formatToolCall** (`(args: { toolName: string; args: unknown; result: unknown; isError: boolean }) => PostableMessage | null`): \*\*已弃用\*\* — 请改用函数形式的 toolDisplay。设置后,它会作为 ToolDisplayFn 运行,并且仅在 result/error 事件中触发;running 和 approval 事件不会渲染。类型层面不能与 toolDisplay 同时使用。 **streaming** (`boolean | { updateIntervalMs?: number }`): 在 Agent 生成文本增量时将其流式传输到 Channel,而不是缓冲后在每个步骤发布一次。要求底层 adapter 支持发布并编辑的流式传输。Slack 默认为 true,其他 adapter 默认为 false。 (Default: `false (true for Slack)`) **textFormat** (`'markdown' | 'plain'`): Agent 最终回复文本的格式。'markdown'(默认值)以 Markdown 发布回复:原生支持 Markdown 渲染的 adapter(如 Slack)会直接渲染,其他 adapter 则转换为平台格式。'plain' 以纯文本发布回复,可让被指示输出 Slack mrkdwn 等平台格式的 Agent 恢复到引入 Markdown 前的行为。此设置仅影响最终回复文本,不影响 Tool 卡片、错误消息和 tripwire 通知。无论此设置为何值,原生 streaming 始终使用 Markdown。 (Default: `'markdown'`) **toolDisplay** (`'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn`): 控制 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 始终渲染为单独的卡片。 (Default: `'cards' ('grouped' for Slack)`) **typingStatus** (`boolean | ((chunk: AgentChunkType, ctx: TypingStatusContext) => string | false | null | undefined | void)`): 控制平台的输入状态指示器。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 到默认状态。 (Default: `true`) ## 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` 传入函数可完全自定义渲染。该函数接收 `ToolDisplayEvent`(`running` / `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 会忽略此选项,并保留现有的纯文本回退行为。 ```typescript 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`)始终渲染为单独的卡片,因为内联任务条目无法包含交互式按钮。 ```typescript 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,可以回退到内置默认值。 ```typescript 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 可以是: - **省略**:使用 Mastra 默认 handler(将消息路由到 Agent 并发布响应) - **`false`**:完全禁用 handler - **函数** `(thread, message, defaultHandler) => Promise`:包装或替换默认 handler ```typescript 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` 函数签名: ```typescript type ChannelHandler = ( thread: Thread, message: Message, defaultHandler: (thread: Thread, message: Message) => Promise, ctx: ChannelHandlerContext, ) => Promise type ChannelHandlerContext = { mastra?: Mastra requestContext: RequestContext } ``` `ctx.mastra` 是解析后的 `mastra` 实例,因此无需传入外部 accessor,handler 即可访问 storage 或其他已注册 primitive: ```typescript onDirectMessage: async (thread, message, defaultHandler, ctx) => { const store = await ctx.mastra?.getStorage()?.getStore('memory') await defaultHandler(thread, message) } ``` `ctx.requestContext` 是此消息即将启动的 run 所用的 [`RequestContext`](https://mastra.zisheng.pro/docs/server/request-context),每条消息都会重新构建。在调用 `defaultHandler` 前写入它,该值就会与 Mastra 随后添加的 Channel 条目一起传入 run: ```typescript onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('locale', 'en-GB') await defaultHandler(thread, message) } ``` 通过这种方式,可以为每条消息决定 run 从其 request context 读取的任何内容,例如平台发送者映射到哪个用户。 ## 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` 可回退到内置行为。 ```typescript 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`): 平台名称(例如 slack、discord)。 **thread** (`Thread`): 消息到达的 Channel thread。使用 thread.isDM 区分私信与群组/Channel thread。 **message** (`Message`): 传入的消息。message.author.userId 是 actor/发送者,不一定是 Memory 所有者。 **defaultResourceId** (`string`): 内置默认值(${platform}:${message.author.userId})。返回此值可保留当前行为。 ## 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` 可保留内置行为。 ```typescript 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`): 平台名称(例如 slack、discord)。 **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 当作视频内容处理。 ```typescript type InlineLinkEntry = | string // Domain pattern (HEAD determines mime type) | { match: string; mimeType: string } // Domain + forced mime type (skips HEAD) ``` ## 相关内容 - [Channels 概览](https://mastra.zisheng.pro/docs/capabilities/channels/overview):概念、快速入门和平台设置 - [Agent 类](https://mastra.zisheng.pro/reference/agents/agent):构造函数参数和方法 - [Chat SDK adapter](https://chat-sdk.dev/adapters):adapter 配置和平台设置