@mastra/core
Version:
383 lines • 17.5 kB
TypeScript
import type { Chat, Adapter, Message, Thread } from 'chat';
import type { Agent } from '../agent/agent.js';
import type { MastraProviderMetadata } from '../agent/message-list/state/types.js';
import type { AgentSignalContents } from '../agent/signals.js';
import type { IMastraLogger } from '../logger/logger.js';
import type { Mastra } from '../mastra/index.js';
import type { StorageThreadType } from '../memory/types.js';
import type { InputProcessor, InputProcessorOrWorkflow, OutputProcessor, OutputProcessorOrWorkflow } from '../processors/index.js';
import { RequestContext } from '../request-context/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;
/** Optional hook to resolve the internal thread id for newly-created channel threads. */
private resolveThreadId;
/**
* 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;
/**
* Id of the entity that owns this channels instance, used in webhook route
* paths. Returns `null` when no owner is bound yet, in which case
* `getWebhookRoutes()` returns no routes.
*/
protected getOwnerId(): string | null;
/** Base path for webhook routes, e.g. `/api/agents/{agentId}`. */
protected getWebhookBasePath(): string;
/** Resolve the Mastra instance from the bound owner. */
protected getMastra(): Mastra | undefined;
/**
* The memory resourceId (owner) used when `processChatMessage` creates a new
* channel-backed thread. Returns a thunk when a `resolveResourceId` hook is
* configured so the hook only runs when a new thread is actually created,
* never when reusing an existing one (which keeps its stored owner).
*/
protected resolveChannelResourceId(args: {
platform: string;
chatThread: Thread;
message: Message;
defaultResourceId: string;
}): string | (() => string | Promise<string>);
/**
* Route an inbound chat message into the owning agent's signal pipeline.
* The message either gets delivered into an already-running agent loop or
* wakes the thread with an idle stream.
*/
protected dispatchInboundMessage(args: {
signalContents: AgentSignalContents;
attributes: Record<string, string | undefined>;
providerOptions: MastraProviderMetadata;
requestContext: RequestContext;
/** The mapped Mastra thread for the chat thread this message arrived on. */
thread: StorageThreadType;
memory: {
thread: string;
resource: string;
};
/** Set when the adapter can't render approval buttons, to avoid runs parking forever. */
autoResumeSuspendedTools: true | undefined;
}): Promise<void>;
/**
* Resume a suspended run with an approval and drive the resumed stream to
* completion (serverless safety).
*/
protected dispatchApproval(args: {
runId: string;
toolCallId: string;
requestContext: RequestContext;
memory: {
thread: string;
resource: string;
};
}): Promise<void>;
/**
* Resume a suspended run with a denial and drive the resumed stream to
* completion (serverless safety).
*/
protected dispatchDecline(args: {
runId: string;
toolCallId: string;
requestContext: RequestContext;
memory: {
thread: string;
resource: string;
};
}): Promise<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 (add_reaction, remove_reaction) that resolve
* the target adapter from the current request context.
*
* These are not injected into the agent automatically — pass them explicitly
* if the agent should react to channel messages:
*
* ```ts
* const agent = new Agent({
* channels,
* tools: { ...channels.getTools() },
* });
* ```
*
* Replies don't need a tool: the agent's response streams back to the
* channel through the output processor.
*/
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;
/**
* Look up a channel-backed Mastra thread and retain the store/metadata needed
* by the create path when no mapping exists.
*/
private findThreadMapping;
/**
* 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. `add_reaction`, not `discord_add_reaction`).
*/
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;
protected log(level: 'info' | 'warn' | 'error' | 'debug', message: string, ...args: unknown[]): void;
}
//# sourceMappingURL=agent-channels.d.ts.map