跳至主要內容

Channel

新增於: @mastra/core@1.22.0

Channel 可將 Agent 連接至訊息平台。請透過 Agent constructor 的 channels 屬性進行設定;傳入的物件為 ChannelConfig。概念與平台設定指示請參閱 Channel 總覽

使用範例
「使用範例」的直接連結

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)作為 key 的平台 adapter。若要使用預設值,請直接傳入 Adapter;若要自訂各 adapter 的選項,請傳入 ChannelAdapterConfig 物件。

handlers?:

ChannelHandlers
覆寫私訊、提及與已訂閱 thread 的預設訊息 handler。

inlineMedia?:

string[] | ((mimeType: string) => boolean)
= ['image/png', 'image/jpeg', 'image/webp', 'application/pdf']
控制哪些附件類型會以檔案 part 傳給模型。不相符的類型會以文字摘要描述。接受 MIME type glob 陣列或 predicate 函式。預設值符合主流視覺模型支援的格式。

tools?:

boolean
= true
getTools() 是否回傳 Channel 專用 Tool(add_reactionremove_reaction)。不支援函式呼叫的模型請設為 false。Channel Tool 絕不會自動加入 Agent,您必須透過 tools: { ...channels.getTools() } 明確傳入。

state?:

StateAdapter
= MastraStateAdapter (from Mastra storage)
用於訂閱與重複資料刪除的 state adapter。預設為由 Mastra 執行個體儲存空間支援的 MastraStateAdapter。Channel 必須設定儲存空間。

userName?:

string
= agent's `name`
平台訊息中顯示的 bot 名稱。預設為 Agent 的 name;若未設定名稱,則為 'Mastra'

threadContext?:

{ maxMessages?: number; addSystemMessage?: boolean }
= { maxMessages: 10, addSystemMessage: true }
Agent 取得目前 thread context 的方式。maxMessages 控制首次提及時要擷取多少則最近的平台訊息(設為 0 可停用;僅適用於非私訊 thread)。addSystemMessage: false 會略過內建 system message;該訊息會告知 Agent request 來自哪個 Channel/平台。

chatOptions?:

Omit<ChatConfig, 'adapters' | 'state' | 'userName'>
直接傳給 Chat SDK 的其他選項。適用於 dedupeTtlMsfallbackStreamingPlaceholderTextlockScopemessageHistory 等進階設定。

resolveResourceId?:

(ctx: ResolveResourceIdContext) => string | Promise<string>
決定哪個 resourceId 擁有 Channel thread 的 resource 層級記憶體,與訊息傳送者分開處理。只會在建立新 thread 時執行;重複使用的 thread 會保留已儲存的擁有者,且絕不呼叫此 hook。回傳 ctx.defaultResourceId${platform}:${message.author.userId})可保留內建行為。

resolveThreadId?:

(ctx: ResolveThreadIdContext) => string | Promise<string>
決定 Channel thread 的 Mastra 內部 thread id。會在 resolveResourceId 之後執行,context 中會提供已解析的擁有者;且只會在建立新 thread 時執行。重複使用的 thread 會保留已儲存的 id,且絕不呼叫此 hook。回傳的 id 在整個記憶體儲存空間中必須唯一;若發生衝突,會改用生成的 id。回傳 ctx.defaultThreadId(隨機 UUID)可保留內建行為。

waitUntil?:

(promise: Promise<unknown>) => void
平台的 waitUntil 函式。Vercel 必須提供此函式,讓背景 Agent run 在 webhook 回傳 200 後仍能繼續。在 Vercel 上,請傳入 @vercel/functionswaitUntil。Cloudflare Workers 與 Netlify Functions 會從 request context 自動偵測。AWS Lambda 會自然等待 event loop 清空,因此不需要 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。若 serverless 部署只需要 webhook 互動,請設為 false

cards?:

boolean
**已棄用** — 請改用 toolDisplay。未設定 toolDisplay 時,cards: true 會對應至 toolDisplay: "cards"cards: false 則對應至 toolDisplay: "text"。IDE 會以刪除線標示此欄位;runtime 行為仍會保留。

cors?:

CorsOptions
此 adapter webhook route 的 CORS 設定。需要跨來源憑證的瀏覽器 Channel adapter 請使用此選項。

formatError?:

(error: Error) => PostableMessage
= "❌ Error: <error.message>"
覆寫 chat 中錯誤的轉譯方式。請回傳對使用者友善的訊息,避免公開原始錯誤。

formatToolCall?:

(args: { toolName: string; args: unknown; result: unknown; isError: boolean }) => PostableMessage | null
**已棄用** — 請改用函式形式的 toolDisplay。設定後會以 ToolDisplayFn 執行,且只在 result/error event 時觸發;runningapproval event 不會轉譯。在型別層級上與 toolDisplay 互斥。

streaming?:

boolean | { updateIntervalMs?: number }
= false (true for Slack)
在 Agent 生成文字 delta 時,直接將其串流至 Channel,而不是先緩衝再於每個步驟發布一次。底層 adapter 必須支援發布並編輯的串流方式。Slack 預設為 true;其他 adapter 預設為 false

textFormat?:

'markdown' | 'plain'
= 'markdown'
Agent 最終回覆文字的方言。'markdown'(預設值)會以 Markdown 發布回覆:具備原生 Markdown 轉譯功能的 adapter(Slack)會直接轉譯,其他 adapter 則轉換為各自的平台格式。'plain' 會以字面純文字發布回覆,讓收到指示要輸出 Slack mrkdwn 等平台方言的 Agent 恢復 Markdown 支援前的行為。只會影響最終回覆文字;Tool card、錯誤訊息與 tripwire 通知不受影響。無論此設定為何,原生串流一律使用 Markdown。

toolDisplay?:

'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn
= 'cards' ('grouped' for Slack)
Tool 呼叫在 Channel 中的轉譯方式。"cards" 會將各 Tool 的執行中/結果 card 以 rich Block Kit 發布;"text" 會以純文字發布相同的生命週期(不使用 Block Kit)。"timeline""grouped" 會將 Tool 狀態以 inline task_update chunk 串流(需要 streaming: true;目前僅支援 Slack,其他 adapter 可能轉譯 placeholder)。"hidden" 會在不顯示的情況下執行 Tool。您可以傳入函式自行轉譯 Tool event;回傳 { kind: "post", message } 可獨立發布/編輯,回傳 { kind: "stream", chunk } 可推送至串流 widget,回傳 undefined 則略過該 event 的轉譯。若 chunk 只應套用至作用中的串流 session,請在 stream 結果加入 openIfEmpty: false。無論模式為何,核准/拒絕 prompt 一律轉譯為獨立 card。

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 會完全隱藏輸入狀態;若即時串流 widget(例如 Slack 中的 toolDisplay: "grouped")已呈現進度,這會很實用。傳入函式可為每個 chunk 設定自訂狀態文字;回傳字串可設定狀態,回傳 false/null/undefined 則維持不變。搭配從 @mastra/core/channels 匯出的 defaultTypingStatus,可讓未處理的 chunk 改用預設值。

Tool 顯示模式
「Tool 顯示模式」的直接連結

toolDisplay 控制 Tool 呼叫在 chat 中的轉譯方式。預設的 'cards' 會為每個 Tool 發布「執行中…」card,並以結果更新該 card,與舊版行為相同。 'text' 採用相同生命週期,但不使用 rich Block Kit,適合無法妥善轉譯 card 的平台。

'timeline''grouped' 會將 Tool 狀態以 inline task_update chunk 和 Agent 文字一同串流。這些模式需要 streaming: true,並依賴 chat adapter 轉譯 chunk。Slack 原生支援這兩種模式;其他 adapter 在新增支援前,可能會轉譯 placeholder。若停用 streaming,Channel 會記錄一次警告,並退回使用 'cards'

'hidden' 會在不顯示的情況下執行 Tool。只有輸入狀態會指出工作正在 進行。

若要完全自訂轉譯,請將函式傳給 toolDisplay。函式會收到 ToolDisplayEventrunning / result / error / approval)與 ToolDisplayContext{ mode, platform });回傳 { kind: 'post', message } 可獨立發布/編輯,回傳 { kind: 'stream', chunk } 可推送至 作用中的串流 widget,回傳 undefined 則略過該 event 的轉譯。

預設情況下,若沒有作用中的串流 session,stream 結果會開啟一個 session。 若 chunk 只適用於現有 session,請設定 openIfEmpty: false。沒有作用中的 session 時,Mastra 會略過該 chunk。靜態 Channel 會忽略此選項,並保留現有的 純文字 fallback 行為。

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)一律轉譯為獨立 card, 因為 inline task 項目無法承載互動式按鈕。

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」的直接連結

覆寫內建 event 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
bot 收到私訊時呼叫。

onMention?:

ChannelHandler | false
bot 在 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 執行個體,因此 handler 不需要傳入外部 accessor,即可存取儲存空間或其他已註冊的 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 的記憶體 resourceId 預設為 ${platform}:${message.author.userId}。記憶體由傳送者擁有,並以平台為範圍。對於單一登入(SSO)等共用身分的應用程式,這會拆分記憶體:同一位使用者在 Feishu 私訊中的 ID 是 feishu:user_123,在網頁上則是 user_123

傳入 resolveResourceId,即可將記憶體擁有權與傳送者分開決定。它只會在建立新 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 是操作者/傳送者,不一定是記憶體擁有者。

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 在整個記憶體儲存空間中必須唯一。若已屬於現有 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 將歸屬的已解析記憶體 resourceId(執行 resolveResourceId 後)。

defaultThreadId:

string
內建預設值(隨機 UUID)。回傳此值可保留目前行為。

Inline media
「Inline media」的直接連結

控制哪些附件類型(圖片、影片、PDF 等)會以檔案 part 傳給模型。不相符的類型會以文字摘要描述,讓 Agent 得知檔案存在,又不會使拒絕不支援類型的模型當機。

預設值(['image/png', 'image/jpeg', 'image/webp', 'application/pdf'])符合主流視覺模型支援的格式。覆寫 inlineMedia 可擴充清單(例如 ['image/*', 'audio/*']),或以 predicate 函式完全取代。

支援的 glob pattern:

pattern相符項目
image/*所有圖片類型(image/pngimage/jpeg 等)
video/*所有影片類型
**/*所有類型
application/pdf完全相符的類型

對於使用私人 CDN 的平台(例如 Slack),會以 Chat SDK 的已驗證憑證擷取附件。對於使用公開 CDN 的平台(例如 Discord),則會將 URL 直接傳給模型。

將訊息文字中的 URL 提升為檔案 part,讓模型能處理連結內容,而不只是看到原始 URL 文字。每個項目可以是字串(domain pattern),或包含強制 MIME type 的物件。

字串項目會比對 domain,並執行 HEAD request 以偵測 Content-Type。解析的類型會與 inlineMedia 比對,只有相符的類型會成為檔案 part。

物件項目會比對 domain 並強制指定 MIME type,同時略過 HEAD request 與 inlineMedia 檢查。這適用於 YouTube 等網站:HEAD request 會回傳 text/html,但模型會將 URL 視為影片內容。

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