import type { ResolvedConfiguredAcpBinding } from "../acp/persistent-bindings.types.js"; import { buildChatChannelMetaById } from "../channels/chat-meta-shared.js"; import type { ChatChannelId } from "../channels/ids.js"; import { emptyChannelConfigSchema } from "../channels/plugins/config-schema.js"; import { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js"; import { createScopedAccountReplyToModeResolver, createTopLevelChannelReplyToModeResolver, } from "../channels/plugins/threading-helpers.js"; import type { ChannelOutboundAdapter, ChannelPairingAdapter, ChannelSecurityAdapter, } from "../channels/plugins/types.adapters.js"; import type { ChannelConfigSchema, ChannelConfigUiHint } from "../channels/plugins/types.config.js"; import type { ChannelMessagingAdapter, ChannelOutboundSessionRoute, ChannelPollResult, ChannelThreadingAdapter, } from "../channels/plugins/types.core.js"; import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; import type { ChannelMeta } from "../channels/plugins/types.public.js"; import type { ReplyToMode } from "../config/types.base.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { buildOutboundBaseSessionKey } from "../infra/outbound/base-session-key.js"; import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import { normalizeOutboundThreadId } from "../infra/outbound/thread-id.js"; import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import type { OpenClawPluginApi } from "../plugins/types.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; import { parseThreadSessionSuffix } from "../sessions/session-key-utils.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, } from "../shared/string-coerce.js"; export type { AgentHarness, AnyAgentTool, MediaUnderstandingProviderPlugin, OpenClawPluginApi, OpenClawPluginCommandDefinition, OpenClawPluginConfigSchema, OpenClawPluginDefinition, OpenClawPluginService, OpenClawPluginServiceContext, PluginCommandContext, PluginCommandResult, PluginAgentEventSubscriptionRegistration, PluginAgentTurnPrepareEvent, PluginAgentTurnPrepareResult, PluginControlUiDescriptor, PluginHeartbeatPromptContributionEvent, PluginHeartbeatPromptContributionResult, PluginJsonValue, PluginNextTurnInjection, PluginNextTurnInjectionEnqueueResult, PluginNextTurnInjectionRecord, PluginRunContextGetParams, PluginRunContextPatch, PluginRuntimeLifecycleRegistration, PluginSessionSchedulerJobHandle, PluginSessionSchedulerJobRegistration, PluginSessionExtensionRegistration, PluginSessionExtensionProjection, PluginToolMetadataRegistration, PluginTrustedToolPolicyRegistration, PluginLogger, ProviderAuthContext, ProviderAuthDoctorHintContext, ProviderAuthMethod, ProviderAuthMethodNonInteractiveContext, ProviderAuthResult, ProviderAugmentModelCatalogContext, ProviderBuildMissingAuthMessageContext, ProviderBuildUnknownModelHintContext, ProviderBuiltInModelSuppressionContext, ProviderBuiltInModelSuppressionResult, ProviderCacheTtlEligibilityContext, ProviderCatalogContext, ProviderCatalogResult, ProviderDefaultThinkingPolicyContext, ProviderDiscoveryContext, ProviderFetchUsageSnapshotContext, ProviderModernModelPolicyContext, ProviderNormalizeResolvedModelContext, ProviderNormalizeToolSchemasContext, ProviderPrepareDynamicModelContext, ProviderPrepareExtraParamsContext, ProviderPrepareRuntimeAuthContext, ProviderPreparedRuntimeAuth, ProviderReasoningOutputMode, ProviderReasoningOutputModeContext, ProviderReplayPolicy, ProviderReplayPolicyContext, ProviderReplaySessionEntry, ProviderReplaySessionState, ProviderResolveDynamicModelContext, ProviderResolveTransportTurnStateContext, ProviderResolveWebSocketSessionPolicyContext, ProviderResolvedUsageAuth, RealtimeTranscriptionProviderPlugin, ProviderSanitizeReplayHistoryContext, ProviderTransportTurnState, ProviderToolSchemaDiagnostic, ProviderResolveUsageAuthContext, ProviderThinkingProfile, ProviderThinkingPolicyContext, ProviderValidateReplayTurnsContext, ProviderWebSocketSessionPolicy, ProviderWrapStreamFnContext, SpeechProviderPlugin, } from "./plugin-entry.js"; export type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js"; export type { OpenClawPluginToolContext, OpenClawPluginToolFactory } from "../plugins/types.js"; export type { MemoryPluginCapability, MemoryPluginPublicArtifact, MemoryPluginPublicArtifactsProvider, } from "../plugins/memory-state.js"; export type { PluginHookReplyDispatchContext, PluginHookReplyDispatchEvent, PluginHookReplyDispatchResult, } from "../plugins/types.js"; export type { OpenClawConfig } from "../config/config.js"; export type { OutboundIdentity } from "../infra/outbound/identity.js"; export type { HistoryEntry } from "../auto-reply/reply/history.js"; export type { ReplyPayload } from "./reply-payload.js"; export type { AllowlistMatch } from "../channels/allowlist-match.js"; export type { BaseProbeResult, ChannelAccountSnapshot, ChannelGroupContext, ChannelMessageActionName, ChannelMeta, ChannelSetupInput, } from "../channels/plugins/types.public.js"; export type { ChatType } from "../channels/chat-type.js"; export type { NormalizedLocation } from "../channels/location.js"; export type { ChannelDirectoryEntry } from "../channels/plugins/types.core.js"; export type { ChannelOutboundAdapter } from "../channels/plugins/types.adapters.js"; export type { PollInput } from "../polls.js"; export { isSecretRef } from "../config/types.secrets.js"; export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js"; export type { ChannelOutboundSessionRoute, ChannelMessagingAdapter, } from "../channels/plugins/types.core.js"; function createInlineTextPairingAdapter(params: { idLabel: string; message: string; normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"]; notify: ( params: Parameters>[0] & { message: string; }, ) => Promise | void; }): ChannelPairingAdapter { return { idLabel: params.idLabel, normalizeAllowEntry: params.normalizeAllowEntry, notifyApproval: async (ctx) => { await params.notify({ ...ctx, message: params.message }); }, }; } export type { ProviderUsageSnapshot, UsageProviderId, UsageWindow, } from "../infra/provider-usage.types.js"; export type { ChannelMessageActionContext } from "../channels/plugins/types.public.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { ChannelConfigUiHint } from "../channels/plugins/types.config.js"; export type { PluginRuntime, RuntimeLogger } from "../plugins/runtime/types.js"; export type { WizardPrompter } from "../wizard/prompts.js"; export { definePluginEntry } from "./plugin-entry.js"; export { buildPluginConfigSchema, emptyPluginConfigSchema } from "../plugins/config-schema.js"; export { KeyedAsyncQueue, enqueueKeyedTask } from "./keyed-async-queue.js"; export { createDedupeCache, resolveGlobalDedupeCache } from "../infra/dedupe.js"; export { generateSecureToken, generateSecureUuid } from "../infra/secure-random.js"; export { buildMemorySystemPromptAddition, delegateCompactionToRuntime, } from "../context-engine/delegate.js"; export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; export { buildChannelConfigSchema, emptyChannelConfigSchema, } from "../channels/plugins/config-schema.js"; export { applyAccountNameToChannelSection, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { clearAccountEntryFields, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from "../channels/plugins/config-helpers.js"; export { formatPairingApproveHint, parseOptionalDelimitedEntries, } from "../channels/plugins/helpers.js"; export { channelTargetSchema, channelTargetsSchema, optionalStringEnum, stringEnum, } from "../agents/schema/typebox.js"; export { DEFAULT_SECRET_FILE_MAX_BYTES, loadSecretFileSync, readSecretFileSync, tryReadSecretFileSync, } from "../infra/secret-file.js"; export type { SecretFileReadOptions, SecretFileReadResult } from "../infra/secret-file.js"; export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js"; export type { GatewayBindUrlResult } from "../shared/gateway-bind-url.js"; export { resolveGatewayPort } from "../config/paths.js"; export { createSubsystemLogger } from "../logging/subsystem.js"; export { normalizeAtHashSlug, normalizeHyphenSlug } from "../shared/string-normalization.js"; export { createActionGate } from "../agents/tools/common.js"; export { jsonResult, readNumberParam, readReactionParams, readStringArrayParam, readStringParam, } from "../agents/tools/common.js"; export { parseStrictPositiveInteger } from "../infra/parse-finite-number.js"; export { isTrustedProxyAddress, resolveClientIp } from "../gateway/net.js"; export { formatZonedTimestamp } from "../infra/format-time/format-datetime.js"; export { resolveConfiguredAcpBindingRecord } from "../acp/persistent-bindings.resolve.js"; export async function ensureConfiguredAcpBindingReady(params: { cfg: OpenClawConfig; configuredBinding: ResolvedConfiguredAcpBinding | null; }): Promise<{ ok: true } | { ok: false; error: string }> { const runtime = await import("../acp/persistent-bindings.lifecycle.js"); return runtime.ensureConfiguredAcpBindingReady(params); } export { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js"; export type { TailscaleStatusCommandResult, TailscaleStatusCommandRunner, } from "../shared/tailscale-status.js"; export { buildAgentSessionKey, type RoutePeer, type RoutePeerKind, } from "../routing/resolve-route.js"; export { resolveThreadSessionKeys } from "../routing/session-key.js"; export type ChannelOutboundSessionRouteParams = Parameters< NonNullable >[0]; let cachedSdkChatChannelMeta: | { cacheKey: string; metaById: ReturnType; } | undefined; function resolveSdkChatChannelMeta(id: string) { const cacheKey = resolveBundledPluginsDir(process.env) ?? ""; if (cachedSdkChatChannelMeta?.cacheKey !== cacheKey) { cachedSdkChatChannelMeta = { cacheKey, metaById: buildChatChannelMetaById(), }; } return cachedSdkChatChannelMeta.metaById[id]; } export function getChatChannelMeta(id: ChatChannelId): ChannelMeta { return resolveSdkChatChannelMeta(id); } /** Remove one of the known provider prefixes from a free-form target string. */ export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string { const trimmed = raw.trim(); for (const provider of providers) { const prefix = `${normalizeLowercaseStringOrEmpty(provider)}:`; if (normalizeLowercaseStringOrEmpty(trimmed).startsWith(prefix)) { return trimmed.slice(prefix.length).trim(); } } return trimmed; } /** Remove generic target-kind prefixes such as `user:` or `group:`. */ export function stripTargetKindPrefix(raw: string): string { return raw.replace(/^(user|channel|group|conversation|room|dm):/i, "").trim(); } /** * Build the canonical outbound session route payload returned by channel * message adapters. */ export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null; peer: { kind: "direct" | "group" | "channel"; id: string }; chatType: "direct" | "group" | "channel"; from: string; to: string; threadId?: string | number; }): ChannelOutboundSessionRoute { const baseSessionKey = buildOutboundBaseSessionKey({ cfg: params.cfg, agentId: params.agentId, channel: params.channel, accountId: params.accountId, peer: params.peer, }); return { sessionKey: baseSessionKey, baseSessionKey, peer: params.peer, chatType: params.chatType, from: params.from, to: params.to, ...(params.threadId !== undefined ? { threadId: params.threadId } : {}), }; } export type ThreadAwareOutboundSessionRouteThreadSource = | "replyToId" | "threadId" | "currentSession"; export type ThreadAwareOutboundSessionRouteRecoveryContext = { route: ChannelOutboundSessionRoute; currentBaseSessionKey: string; currentThreadId: string; }; export function recoverCurrentThreadSessionId(params: { route: ChannelOutboundSessionRoute; currentSessionKey?: string | null; canRecover?: (context: ThreadAwareOutboundSessionRouteRecoveryContext) => boolean; }): string | undefined { const current = parseThreadSessionSuffix(params.currentSessionKey); if (!current.baseSessionKey || !current.threadId) { return undefined; } if ( normalizeOptionalLowercaseString(current.baseSessionKey) !== normalizeOptionalLowercaseString(params.route.baseSessionKey) ) { return undefined; } const context = { route: params.route, currentBaseSessionKey: current.baseSessionKey, currentThreadId: current.threadId, }; if (params.canRecover && !params.canRecover(context)) { return undefined; } return current.threadId; } export function buildThreadAwareOutboundSessionRoute(params: { route: ChannelOutboundSessionRoute; replyToId?: string | number | null; threadId?: string | number | null; currentSessionKey?: string | null; precedence?: readonly ThreadAwareOutboundSessionRouteThreadSource[]; useSuffix?: boolean; parentSessionKey?: string; normalizeThreadId?: (threadId: string) => string; canRecoverCurrentThread?: (context: ThreadAwareOutboundSessionRouteRecoveryContext) => boolean; }): ChannelOutboundSessionRoute { const recoveredThreadId = recoverCurrentThreadSessionId({ route: params.route, currentSessionKey: params.currentSessionKey, canRecover: params.canRecoverCurrentThread, }); const candidates: Record< ThreadAwareOutboundSessionRouteThreadSource, { routeThreadId: string | number; sessionThreadId: string } | undefined > = { replyToId: resolveThreadAwareOutboundCandidate(params.replyToId), threadId: resolveThreadAwareOutboundCandidate(params.threadId), currentSession: resolveThreadAwareOutboundCandidate(recoveredThreadId), }; const precedence = params.precedence ?? ["replyToId", "threadId", "currentSession"]; const candidate = precedence.map((source) => candidates[source]).find(Boolean); const threadKeys = resolveThreadSessionKeys({ baseSessionKey: params.route.baseSessionKey, threadId: candidate?.sessionThreadId, parentSessionKey: candidate ? params.parentSessionKey : undefined, useSuffix: params.useSuffix, normalizeThreadId: params.normalizeThreadId, }); return { ...params.route, sessionKey: threadKeys.sessionKey, ...(candidate !== undefined ? { threadId: candidate.routeThreadId } : {}), }; } function resolveThreadAwareOutboundCandidate( threadId?: string | number | null, ): { routeThreadId: string | number; sessionThreadId: string } | undefined { const sessionThreadId = normalizeOutboundThreadId(threadId); if (sessionThreadId === undefined) { return undefined; } return { routeThreadId: typeof threadId === "number" ? threadId : sessionThreadId, sessionThreadId, }; } /** Options for a channel plugin entry that should register a channel capability. */ type ChannelEntryConfigSchema = TPlugin extends ChannelPlugin ? NonNullable : ChannelConfigSchema; type DefineChannelPluginEntryOptions = { id: string; name: string; description: string; plugin: TPlugin; configSchema?: ChannelEntryConfigSchema | (() => ChannelEntryConfigSchema); setRuntime?: (runtime: PluginRuntime) => void; registerCliMetadata?: (api: OpenClawPluginApi) => void; registerFull?: (api: OpenClawPluginApi) => void; }; type DefinedChannelPluginEntry = { id: string; name: string; description: string; configSchema: ChannelEntryConfigSchema; register: (api: OpenClawPluginApi) => void; channelPlugin: TPlugin; setChannelRuntime?: (runtime: PluginRuntime) => void; }; type CreateChannelPluginBaseOptions = { id: ChannelPlugin["id"]; meta?: Partial["meta"]>>; setupWizard?: NonNullable["setupWizard"]>; capabilities?: ChannelPlugin["capabilities"]; commands?: ChannelPlugin["commands"]; doctor?: ChannelPlugin["doctor"]; agentPrompt?: ChannelPlugin["agentPrompt"]; streaming?: ChannelPlugin["streaming"]; reload?: ChannelPlugin["reload"]; gatewayMethods?: ChannelPlugin["gatewayMethods"]; configSchema?: ChannelPlugin["configSchema"]; config?: ChannelPlugin["config"]; security?: ChannelPlugin["security"]; setup: NonNullable["setup"]>; groups?: ChannelPlugin["groups"]; }; type CreatedChannelPluginBase = Pick< ChannelPlugin, "id" | "meta" | "setup" > & Partial< Pick< ChannelPlugin, | "setupWizard" | "capabilities" | "commands" | "doctor" | "agentPrompt" | "streaming" | "reload" | "gatewayMethods" | "configSchema" | "config" | "security" | "groups" > >; /** * Canonical entry helper for channel plugins. * * This wraps `definePluginEntry(...)`, registers the channel capability, and * optionally exposes extra full-runtime registration such as tools or gateway * handlers that only make sense outside setup-only registration modes. */ export function defineChannelPluginEntry({ id, name, description, plugin, configSchema, setRuntime, registerCliMetadata, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry { const resolvedConfigSchema: ChannelEntryConfigSchema = typeof configSchema === "function" ? configSchema() : ((configSchema ?? emptyChannelConfigSchema()) as ChannelEntryConfigSchema); const entry = { id, name, description, configSchema: resolvedConfigSchema, register(api: OpenClawPluginApi) { if (api.registrationMode === "cli-metadata") { registerCliMetadata?.(api); return; } if (api.registrationMode === "tool-discovery") { registerFull?.(api); return; } api.registerChannel({ plugin: plugin as ChannelPlugin }); setRuntime?.(api.runtime); if (api.registrationMode === "discovery") { registerCliMetadata?.(api); return; } if (api.registrationMode !== "full") { return; } registerCliMetadata?.(api); registerFull?.(api); }, }; return { ...entry, channelPlugin: plugin, ...(setRuntime ? { setChannelRuntime: setRuntime } : {}), }; } /** * Minimal setup-entry helper for channels that ship a separate `setup-entry.ts`. * * The setup entry only needs to export `{ plugin }`, but using this helper * keeps the shape explicit in examples and generated typings. */ export function defineSetupPluginEntry(plugin: TPlugin) { return { plugin }; } type ChatChannelPluginBase = Omit< ChannelPlugin, "security" | "pairing" | "threading" | "outbound" > & Partial< Pick< ChannelPlugin, "security" | "pairing" | "threading" | "outbound" > >; type ChatChannelSecurityOptions = { dm: { channelKey: string; resolvePolicy: (account: TResolvedAccount) => string | null | undefined; resolveAllowFrom: (account: TResolvedAccount) => Array | null | undefined; resolveFallbackAccountId?: (account: TResolvedAccount) => string | null | undefined; defaultPolicy?: string; allowFromPathSuffix?: string; policyPathSuffix?: string; approveChannelId?: string; approveHint?: string; normalizeEntry?: (raw: string) => string; inheritSharedDefaultsFromDefaultAccount?: boolean; }; collectWarnings?: ChannelSecurityAdapter["collectWarnings"]; collectAuditFindings?: ChannelSecurityAdapter["collectAuditFindings"]; }; type ChatChannelPairingOptions = { text: { idLabel: string; message: string; normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"]; notify: ( params: Parameters>[0] & { message: string; }, ) => Promise | void; }; }; type ChatChannelThreadingReplyModeOptions = | { topLevelReplyToMode: string } | { scopedAccountReplyToMode: { resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) => TResolvedAccount; resolveReplyToMode: ( account: TResolvedAccount, chatType?: string | null, ) => ReplyToMode | null | undefined; fallback?: ReplyToMode; }; } | { resolveReplyToMode: NonNullable; }; type ChatChannelThreadingOptions = ChatChannelThreadingReplyModeOptions & Omit; type ChatChannelAttachedOutboundOptions = { base: Omit; attachedResults: { channel: string; sendText?: ( ctx: Parameters>[0], ) => MaybePromise>; sendMedia?: ( ctx: Parameters>[0], ) => MaybePromise>; sendPoll?: ( ctx: Parameters>[0], ) => MaybePromise>; }; }; type MaybePromise = T | Promise; function createInlineAttachedChannelResultAdapter( params: ChatChannelAttachedOutboundOptions["attachedResults"], ) { return { sendText: params.sendText ? async (ctx: Parameters>[0]) => ({ channel: params.channel, ...(await params.sendText!(ctx)), }) : undefined, sendMedia: params.sendMedia ? async (ctx: Parameters>[0]) => ({ channel: params.channel, ...(await params.sendMedia!(ctx)), }) : undefined, sendPoll: params.sendPoll ? async (ctx: Parameters>[0]) => ({ channel: params.channel, ...(await params.sendPoll!(ctx)), }) : undefined, } satisfies Pick; } function resolveChatChannelSecurity( security: | ChannelSecurityAdapter | ChatChannelSecurityOptions | undefined, ): ChannelSecurityAdapter | undefined { if (!security) { return undefined; } if (!("dm" in security)) { return security; } return { resolveDmPolicy: ({ cfg, accountId, account }) => buildAccountScopedDmSecurityPolicy({ cfg, channelKey: security.dm.channelKey, accountId, fallbackAccountId: security.dm.resolveFallbackAccountId?.(account) ?? account.accountId, policy: security.dm.resolvePolicy(account), allowFrom: security.dm.resolveAllowFrom(account) ?? [], defaultPolicy: security.dm.defaultPolicy, allowFromPathSuffix: security.dm.allowFromPathSuffix, policyPathSuffix: security.dm.policyPathSuffix, approveChannelId: security.dm.approveChannelId, approveHint: security.dm.approveHint, normalizeEntry: security.dm.normalizeEntry, inheritSharedDefaultsFromDefaultAccount: security.dm.inheritSharedDefaultsFromDefaultAccount, }), ...(security.collectWarnings ? { collectWarnings: security.collectWarnings } : {}), ...(security.collectAuditFindings ? { collectAuditFindings: security.collectAuditFindings } : {}), }; } function resolveChatChannelPairing( pairing: ChannelPairingAdapter | ChatChannelPairingOptions | undefined, ): ChannelPairingAdapter | undefined { if (!pairing) { return undefined; } if (!("text" in pairing)) { return pairing; } return createInlineTextPairingAdapter(pairing.text); } function resolveChatChannelThreading( threading: ChannelThreadingAdapter | ChatChannelThreadingOptions | undefined, ): ChannelThreadingAdapter | undefined { if (!threading) { return undefined; } if (!("topLevelReplyToMode" in threading) && !("scopedAccountReplyToMode" in threading)) { return threading; } let resolveReplyToMode: ChannelThreadingAdapter["resolveReplyToMode"]; if ("topLevelReplyToMode" in threading) { resolveReplyToMode = createTopLevelChannelReplyToModeResolver(threading.topLevelReplyToMode); } else { resolveReplyToMode = createScopedAccountReplyToModeResolver( threading.scopedAccountReplyToMode, ); } return { ...threading, resolveReplyToMode, }; } function resolveChatChannelOutbound( outbound: ChannelOutboundAdapter | ChatChannelAttachedOutboundOptions | undefined, ): ChannelOutboundAdapter | undefined { if (!outbound) { return undefined; } if (!("attachedResults" in outbound)) { return outbound; } return { ...outbound.base, ...createInlineAttachedChannelResultAdapter(outbound.attachedResults), }; } // Shared higher-level builder for chat-style channels that mostly compose // scoped DM security, text pairing, reply threading, and attached send results. export function createChatChannelPlugin< TResolvedAccount extends { accountId?: string | null }, Probe = unknown, Audit = unknown, >(params: { base: ChatChannelPluginBase; security?: | ChannelSecurityAdapter | ChatChannelSecurityOptions; pairing?: ChannelPairingAdapter | ChatChannelPairingOptions; threading?: ChannelThreadingAdapter | ChatChannelThreadingOptions; outbound?: ChannelOutboundAdapter | ChatChannelAttachedOutboundOptions; }): ChannelPlugin { return { ...params.base, conversationBindings: { supportsCurrentConversationBinding: true, ...params.base.conversationBindings, }, ...(params.security ? { security: resolveChatChannelSecurity(params.security) } : {}), ...(params.pairing ? { pairing: resolveChatChannelPairing(params.pairing) } : {}), ...(params.threading ? { threading: resolveChatChannelThreading(params.threading) } : {}), ...(params.outbound ? { outbound: resolveChatChannelOutbound(params.outbound) } : {}), } as ChannelPlugin; } // Shared base object for channel plugins that only need to override a few optional surfaces. export function createChannelPluginBase( params: CreateChannelPluginBaseOptions, ): CreatedChannelPluginBase { return { id: params.id, meta: { ...resolveSdkChatChannelMeta(params.id), ...params.meta, }, ...(params.setupWizard ? { setupWizard: params.setupWizard } : {}), ...(params.capabilities ? { capabilities: params.capabilities } : {}), ...(params.commands ? { commands: params.commands } : {}), ...(params.doctor ? { doctor: params.doctor } : {}), ...(params.agentPrompt ? { agentPrompt: params.agentPrompt } : {}), ...(params.streaming ? { streaming: params.streaming } : {}), ...(params.reload ? { reload: params.reload } : {}), ...(params.gatewayMethods ? { gatewayMethods: params.gatewayMethods } : {}), ...(params.configSchema ? { configSchema: params.configSchema } : {}), ...(params.config ? { config: params.config } : {}), ...(params.security ? { security: params.security } : {}), ...(params.groups ? { groups: params.groups } : {}), setup: params.setup, } as CreatedChannelPluginBase; }