UNPKG

@mastra/core

Version:
383 lines 17.5 kB
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