跳至主要內容

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>
以名稱(例如 slack、discord)作為 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_reaction、remove_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 的其他選項。適用於 dedupeTtlMs、fallbackStreamingPlaceholderText、lockScope 與 messageHistory 等進階設定。

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/functions 的 waitUntil。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。解析順序:獨立 waitUntil → resolveWaitUntil(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 時觸發;running 與 approval 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。函式會收到 ToolDisplayEvent(running / 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
平台名稱(例如 slack、discord)。

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
平台名稱(例如 slack、discord)。

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/png、image/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)