@mastra/core
Version:
292 lines • 13.8 kB
TypeScript
import type { Chat, Adapter, Thread } from 'chat';
import type { Agent } from '../agent/agent.js';
import type { IMastraLogger } from '../logger/logger.js';
import type { Mastra } from '../mastra/index.js';
import type { InputProcessor, InputProcessorOrWorkflow, OutputProcessor, OutputProcessorOrWorkflow } from '../processors/index.js';
import type { ApiRoute } from '../server/types.js';
import type { ChatChannelRenderContext } from './output-processor.js';
import type { ChannelAdapterConfig, ChannelConfig } from './types.js';
/**
* Manages a single Chat SDK instance for an agent, wiring all adapters
* to the Mastra pipeline (thread mapping → agent.stream → thread.post).
*
* One AgentChannels = one bot identity across multiple platforms.
*
* @internal Created automatically by the Agent when `channels` config is provided.
*/
export declare class AgentChannels {
readonly adapters: Record<string, Adapter>;
private chat;
/** Stored initialization promise so webhook handlers can await readiness on serverless cold starts. */
private initPromise;
private agent;
private logger?;
private customState;
private stateAdapter;
private userName;
/** Normalized per-adapter configs (gateway flags, hooks, etc.). */
private adapterConfigs;
/** Handler overrides from config. */
private handlerOverrides;
/** Additional Chat SDK options. */
private chatOptions;
/** Thread context config for fetching prior messages. */
private threadContext;
/** Determines whether a mime type should be sent inline to the model. */
private shouldInline;
/** Inline-link rules for promoting URLs in message text to file parts. */
private inlineLinkRules;
/** Whether channel tools (reactions, etc.) are enabled. */
private toolsEnabled;
/** Optional hook to resolve the memory resourceId (owner) for newly-created channel threads. */
private resolveResourceId;
/**
* The original `ChannelConfig` passed to the constructor.
*
* Useful for rebuilding `AgentChannels` while preserving existing adapters/handlers,
* e.g. when a `ChannelProvider` wants to inject its own adapter without clobbering
* adapters configured by the agent author:
*
* @example
* ```ts
* const existing = agent.getChannels();
* const next = new AgentChannels({
* ...existing?.channelConfig,
* adapters: { ...existing?.channelConfig.adapters, slack: slackAdapter },
* });
* agent.setChannels(next);
* ```
*/
readonly channelConfig: ChannelConfig;
/** Channel tool names whose effects are already visible on the platform (skip rendering cards). */
private channelToolNames;
/** Platforms whose routes are managed externally (e.g., by SlackProvider). */
private externallyManagedPlatforms;
/**
* Tool-approval cards that have been posted and are awaiting user action. When the user
* clicks approve/decline, the `onAction` handler looks up the card's `messageId` and
* tool metadata here so it can edit the card in place and resume the run with the right
* context. Entries are removed after the resume completes.
*/
private pendingApprovalCards;
/**
* Platforms we've already warned about for misconfigured `toolDisplay` (e.g.
* `'timeline'` without `streaming: true`). Keeps log output to one warn per
* platform per AgentChannels instance.
*/
private warnedToolDisplayFallback;
constructor(config: ChannelConfig);
/**
* Bind this AgentChannels to its owning agent. Called by Agent constructor.
* @internal
*/
__setAgent(agent: Agent<any, any, any, any>): void;
/**
* Set the logger. Called by Mastra.addAgent.
* @internal
*/
__setLogger(logger: IMastraLogger): void;
/**
* Register an adapter dynamically.
* When `managesRoutes` is true, AgentChannels will NOT create webhook routes for this platform
* (the ChannelProvider handles routing and calls handleWebhookEvent directly).
* @internal
*/
__registerAdapter(platform: string, adapter: Adapter, config?: ChannelAdapterConfig, options?: {
managesRoutes?: boolean;
}): void;
/**
* Check if an adapter is registered for the given platform.
*/
hasAdapter(platform: string): boolean;
/**
* Get the underlying Chat SDK instance.
* Available after Mastra initialization. Use this to register additional
* event handlers or access adapter-specific methods.
*
* @example
* ```ts
* agent.channels.sdk.onReaction((thread, reaction) => {
* console.log('Reaction received:', reaction);
* });
* ```
*/
get sdk(): Chat | null;
/**
* Initialize the Chat SDK, register handlers, and start gateway listeners.
* Called by Mastra.addAgent after the server is ready.
*/
initialize(mastra: Mastra): Promise<void>;
/**
* Returns API routes for receiving webhook events from each adapter.
* One POST route per adapter at `/api/agents/{agentId}/channels/{platform}/webhook`.
* Skips platforms that are externally managed (e.g., by SlackProvider).
*/
getWebhookRoutes(): ApiRoute[];
/**
* Handle a webhook event from an external source (e.g., SlackProvider).
* Use this when a ChannelProvider manages its own routes but wants AgentChannels
* to process the actual message handling (threading, agent responses, etc.).
*
* @param platform - The platform name (e.g., 'slack')
* @param request - The raw HTTP request
* @param options - Optional execution context for serverless environments
* @returns The response from the Chat SDK webhook handler
*/
handleWebhookEvent(platform: string, request: Request, options?: {
waitUntil?: (p: Promise<unknown>) => void;
}): Promise<Response>;
/**
* Returns channel input processors (e.g. system prompt injection).
*
* - Skipped entirely when `channels.threadContext.addSystemMessage` is `false`.
* - Skipped if the user already added a processor with the same id.
*/
getInputProcessors(configuredProcessors?: InputProcessorOrWorkflow[]): InputProcessor[];
/**
* Returns channel output processors that render the agent's stream to the
* originating chat platform. The processor resolves its render context from
* the inbound `requestContext` marker set by `processChatMessage` when
* present, and otherwise reconstructs it from the run's thread via the bound
* `AgentChannels` (so schedule / Studio / custom-UI runs on a channel-backed
* thread still post back). Non-channel runs pass through untouched.
*
* Skipped if the user already added a processor with the same id.
*/
/**
* @deprecated No longer needed — `AgentChannels` no longer holds stateful resources that require cleanup.
* Kept as a no-op for backwards compatibility with existing `ChannelProvider` implementations.
*/
close(): void;
getOutputProcessors(configuredProcessors?: OutputProcessorOrWorkflow[]): OutputProcessor[];
/**
* Returns generic channel tools (send_message, add_reaction, etc.)
* that resolve the target adapter from the current request context.
*/
getTools(): Record<string, unknown>;
/**
* Resolve the adapter for the current conversation from request context.
*/
private getAdapterFromContext;
/**
* Derive the three per-event shapes we hand off to downstream systems from one set of
* inputs. Keeping this in one place ensures the LLM (`attributes`), input processors
* (`requestContext`), and memory (`metadata`) all see consistent author / thread facts.
*
* - `channelContext` — goes on `requestContext` under the 'channel' key, consumed by
* `ChatChannelProcessor` and other input processors.
* - `attributes` — serialized as XML on the user message element the LLM sees (e.g. on
* `<user messageId=... authorId=... />`). Strings only.
* - `providerOptions` — written to the stored message's `content.providerMetadata`
* under `mastra.channels.<platform>` so UI/query callers can read author/channel
* facts off the message (e.g. show a Slack icon + author name) without unpacking
* the signal envelope. The LLM ignores `providerOptions.mastra.*` since only
* provider-keyed entries (openai, anthropic, …) are forwarded to the model.
*/
/**
* Resolve the external thread id to use when looking up a Mastra thread for
* a tool-approval flow. Dispatches to per-platform compat shims that work
* around quirks in how adapters surface threading on inbound action events.
* Add new platform branches here as their compat shims land in `./compat/*`.
*/
private resolveExternalThreadId;
private buildEventContext;
/**
* Core handler wired to Chat SDK's onDirectMessage, onNewMention,
* and onSubscribedMessage. Streams the Mastra agent response and
* updates the channel message in real-time via edits.
*/
private handleChatMessage;
private processChatMessage;
/**
* Fetch recent messages from the platform thread to provide context.
* Returns messages in chronological order (oldest first), excluding the
* current triggering message.
*/
private fetchThreadHistory;
/**
* Build the per-event render dependencies stashed on `requestContext` for
* `ChatChannelOutputProcessor`. Captures the adapter, driver mode,
* tool-display config, approval-card stash callbacks, and the typing-status
* wrapper as a callable so the processor can apply it after the queue is
* created. The returned object is plain data — no streams, no promises —
* so it's safe to stash on `requestContext` for the processor to read later.
*
* @internal Used by `processChatMessage` and the approve/decline paths.
*/
_buildRenderContext(chatThread: Thread, platform: string, approvalContext?: {
toolCallId: string;
messageId: string;
}): ChatChannelRenderContext;
/**
* Reconstruct a {@link ChatChannelRenderContext} for a Mastra thread that is
* backed by a channel, without an inbound platform event.
*
* The inbound webhook paths (`processChatMessage`, approve/decline) stash a
* render context on `requestContext` because they already hold the live
* `Thread` handle from `event.thread`. Runs that did NOT originate from a
* platform message (schedule fires, Studio, custom UI, user code) have no such
* handle, so `ChatChannelOutputProcessor` calls this to rebuild it from the
* thread's persisted channel coordinates.
*
* Returns `null` when the thread is not channel-backed (no `channel_platform`
* metadata) or its platform adapter isn't configured on this instance — the
* processor then passes the run through untouched.
*
* Delegates to the same {@link _buildRenderContext} used by the inbound paths,
* so both paths produce an identical render context (single source of truth).
* The only per-fire inputs are `platform` (a persisted string) and the live
* `Thread` handle, which the Chat SDK materializes from the stored external id
* via `chat.thread(externalThreadId)`.
*/
buildRenderContextForThread(threadId: string): Promise<ChatChannelRenderContext | null>;
/**
* Normalize the per-adapter `streaming` option (`boolean | { updateIntervalMs? }`)
* into a flat `{ enabled, options }` shape so call-sites don't have to
* re-derive both from the raw union.
*/
private resolveStreaming;
/**
* Pass-through async generator that yields chunks unchanged but emits
* typing-status updates (`startTyping`) along the way. Lives outside the
* drivers so both drivers benefit from the same dedup + gate logic.
*
* The streaming driver flips `typingGate.active = true` while a
* `StreamingPlan` post is in flight — Slack's `assistant.threads.setStatus`
* (what `startTyping` maps to) only auto-clears on `chat.postMessage`, not
* on `chat.stopStream`, so a status set during streaming would stick after
* the run ends. The static driver leaves the gate `false` so typing works
* normally in cards/hidden modes.
*/
private withTypingStatus;
/**
* Resolves an existing Mastra thread for the given external IDs, or creates one.
*/
private getOrCreateThread;
/**
* Generate generic channel tools that resolve the adapter from request context.
* Tool names are platform-agnostic (e.g. `send_message`, not `discord_send_message`).
*/
private makeChannelTools;
/**
* Persistent reconnection loop for Gateway-based adapters (e.g. Discord).
*/
private startGatewayLoop;
/**
* Resolve the tool-display mode for a run.
*
* - `'timeline'` / `'grouped'` push `task_update` chunks into a streaming
* Plan widget, so they require `streaming: true`. Without streaming we
* fall back to `'cards'`.
* - `'cards'` posts discrete Block-Kit cards via `chatThread.post`/`edit`,
* which the streaming driver doesn't render (everything inside a
* `StreamingPlan` post is one message). With streaming enabled we fall
* back to `'timeline'`.
*
* Both fallbacks log a one-time warning per platform so the misconfiguration
* is visible without spamming on every run.
*/
private resolveToolDisplay;
private log;
}
//# sourceMappingURL=agent-channels.d.ts.map