> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-TW/llms.txt # Channel **新增於:** `@mastra/core@1.22.0` Channel 可將 Agent 連接至訊息平台。請透過 `Agent` constructor 的 `channels` 屬性進行設定;傳入的物件為 `ChannelConfig`。概念與平台設定指示請參閱 [Channel 總覽](https://mastra.zisheng.pro/zh-TW/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)作為 key 的平台 adapter。若要使用預設值,請直接傳入 Adapter;若要自訂各 adapter 的選項,請傳入 ChannelAdapterConfig 物件。 **handlers** (`ChannelHandlers`): 覆寫私訊、提及與已訂閱 thread 的預設訊息 handler。 **inlineMedia** (`string[] | ((mimeType: string) => boolean)`): 控制哪些附件類型會以檔案 part 傳給模型。不相符的類型會以文字摘要描述。接受 MIME type glob 陣列或 predicate 函式。預設值符合主流視覺模型支援的格式。 (Default: `['image/png', 'image/jpeg', 'image/webp', 'application/pdf']`) **inlineLinks** (`InlineLinkEntry[]`): 將訊息文字中的 URL 提升為檔案 part,讓模型能處理連結內容。每個項目會比對一個 domain。預設停用。 **tools** (`boolean`): getTools() 是否回傳 Channel 專用 Tool(add\_reaction、remove\_reaction)。不支援函式呼叫的模型請設為 false。Channel Tool 絕不會自動加入 Agent,您必須透過 tools: { ...channels.getTools() } 明確傳入。 (Default: `true`) **state** (`StateAdapter`): 用於訂閱與重複資料刪除的 state adapter。預設為由 Mastra 執行個體儲存空間支援的 MastraStateAdapter。Channel 必須設定儲存空間。 (Default: `MastraStateAdapter (from Mastra storage)`) **userName** (`string`): 平台訊息中顯示的 bot 名稱。預設為 Agent 的 name;若未設定名稱,則為 'Mastra'。 (Default: `` agent's `name` ``) **threadContext** (`{ maxMessages?: number; addSystemMessage?: boolean }`): Agent 取得目前 thread context 的方式。maxMessages 控制首次提及時要擷取多少則最近的平台訊息(設為 0 可停用;僅適用於非私訊 thread)。addSystemMessage: false 會略過內建 system message;該訊息會告知 Agent request 來自哪個 Channel/平台。 (Default: `{ maxMessages: 10, addSystemMessage: true }`) **chatOptions** (`Omit`): 直接傳給 Chat SDK 的其他選項。適用於 dedupeTtlMs、fallbackStreamingPlaceholderText、lockScope 與 messageHistory 等進階設定。 **resolveResourceId** (`(ctx: ResolveResourceIdContext) => string | Promise`): 決定哪個 resourceId 擁有 Channel thread 的 resource 層級記憶體,與訊息傳送者分開處理。只會在建立新 thread 時執行;重複使用的 thread 會保留已儲存的擁有者,且絕不呼叫此 hook。回傳 ctx.defaultResourceId(${platform}:${message.author.userId})可保留內建行為。 **resolveThreadId** (`(ctx: ResolveThreadIdContext) => string | Promise`): 決定 Channel thread 的 Mastra 內部 thread id。會在 resolveResourceId 之後執行,context 中會提供已解析的擁有者;且只會在建立新 thread 時執行。重複使用的 thread 會保留已儲存的 id,且絕不呼叫此 hook。回傳的 id 在整個記憶體儲存空間中必須唯一;若發生衝突,會改用生成的 id。回傳 ctx.defaultThreadId(隨機 UUID)可保留內建行為。 **waitUntil** (`(promise: Promise) => 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) => 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。若 serverless 部署只需要 webhook 互動,請設為 false。 (Default: `true`) **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`): 覆寫 chat 中錯誤的轉譯方式。請回傳對使用者友善的訊息,避免公開原始錯誤。 (Default: `"❌ Error: "`) **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 }`): 在 Agent 生成文字 delta 時,直接將其串流至 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 card、錯誤訊息與 tripwire 通知不受影響。無論此設定為何,原生串流一律使用 Markdown。 (Default: `'markdown'`) **toolDisplay** (`'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn`): 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。 (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 會完全隱藏輸入狀態;若即時串流 widget(例如 Slack 中的 toolDisplay: "grouped")已呈現進度,這會很實用。傳入函式可為每個 chunk 設定自訂狀態文字;回傳字串可設定狀態,回傳 false/null/undefined 則維持不變。搭配從 @mastra/core/channels 匯出的 defaultTypingStatus,可讓未處理的 chunk 改用預設值。 (Default: `true`) ## 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 行為。 ```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`)一律轉譯為獨立 card, 因為 inline task 項目無法承載互動式按鈕。 ```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 覆寫內建 event 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`): bot 收到私訊時呼叫。 **onMention** (`ChannelHandler | false`): bot 在 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` 執行個體,因此 handler 不需要傳入外部 accessor,即可存取儲存空間或其他已註冊的 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/zh-TW/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 的記憶體 `resourceId` 預設為 `${platform}:${message.author.userId}`。記憶體由傳送者擁有,並以平台為範圍。對於單一登入(SSO)等共用身分的應用程式,這會拆分記憶體:同一位使用者在 Feishu 私訊中的 ID 是 `feishu:user_123`,在網頁上則是 `user_123`。 傳入 `resolveResourceId`,即可將記憶體擁有權與傳送者分開決定。它只會在建立新 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 是操作者/傳送者,不一定是記憶體擁有者。 **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 在整個記憶體儲存空間中必須唯一。若已屬於現有 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 將歸屬的已解析記憶體 resourceId(執行 resolveResourceId 後)。 **defaultThreadId** (`string`): 內建預設值(隨機 UUID)。回傳此值可保留目前行為。 ## 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 直接傳給模型。 ## Inline link 將訊息文字中的 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 視為影片內容。 ```typescript type InlineLinkEntry = | string // Domain pattern (HEAD determines mime type) | { match: string; mimeType: string } // Domain + forced mime type (skips HEAD) ``` ## 相關內容 - [Channel 總覽](https://mastra.zisheng.pro/zh-TW/docs/capabilities/channels/overview):概念、快速入門與平台設定 - [Agent 類別](https://mastra.zisheng.pro/zh-TW/reference/agents/agent):constructor 參數與方法 - [Chat SDK adapter](https://chat-sdk.dev/adapters):adapter 設定與平台設定