import type { FinalizedMsgContext } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/config.js"; import type { PluginHookMessageContext, PluginHookMessageReceivedEvent, PluginHookMessageSentEvent, } from "../plugins/types.js"; import type { MessagePreprocessedHookContext, MessageReceivedHookContext, MessageSentHookContext, MessageTranscribedHookContext, } from "./internal-hooks.js"; export type CanonicalInboundMessageHookContext = { from: string; to?: string; content: string; body?: string; bodyForAgent?: string; transcript?: string; timestamp?: number; channelId: string; accountId?: string; conversationId?: string; messageId?: string; senderId?: string; senderName?: string; senderUsername?: string; senderE164?: string; provider?: string; surface?: string; threadId?: string | number; mediaPath?: string; mediaType?: string; originatingChannel?: string; originatingTo?: string; guildId?: string; channelName?: string; isGroup: boolean; groupId?: string; }; export type CanonicalSentMessageHookContext = { to: string; content: string; success: boolean; error?: string; channelId: string; accountId?: string; conversationId?: string; messageId?: string; isGroup?: boolean; groupId?: string; }; export function deriveInboundMessageHookContext( ctx: FinalizedMsgContext, overrides?: { content?: string; messageId?: string; }, ): CanonicalInboundMessageHookContext { const content = overrides?.content ?? (typeof ctx.BodyForCommands === "string" ? ctx.BodyForCommands : typeof ctx.RawBody === "string" ? ctx.RawBody : typeof ctx.Body === "string" ? ctx.Body : ""); const channelId = (ctx.OriginatingChannel ?? ctx.Surface ?? ctx.Provider ?? "").toLowerCase(); const conversationId = ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? undefined; const isGroup = Boolean(ctx.GroupSubject || ctx.GroupChannel); return { from: ctx.From ?? "", to: ctx.To, content, body: ctx.Body, bodyForAgent: ctx.BodyForAgent, transcript: ctx.Transcript, timestamp: typeof ctx.Timestamp === "number" && Number.isFinite(ctx.Timestamp) ? ctx.Timestamp : undefined, channelId, accountId: ctx.AccountId, conversationId, messageId: overrides?.messageId ?? ctx.MessageSidFull ?? ctx.MessageSid ?? ctx.MessageSidFirst ?? ctx.MessageSidLast, senderId: ctx.SenderId, senderName: ctx.SenderName, senderUsername: ctx.SenderUsername, senderE164: ctx.SenderE164, provider: ctx.Provider, surface: ctx.Surface, threadId: ctx.MessageThreadId, mediaPath: ctx.MediaPath, mediaType: ctx.MediaType, originatingChannel: ctx.OriginatingChannel, originatingTo: ctx.OriginatingTo, guildId: ctx.GroupSpace, channelName: ctx.GroupChannel, isGroup, groupId: isGroup ? conversationId : undefined, }; } export function buildCanonicalSentMessageHookContext(params: { to: string; content: string; success: boolean; error?: string; channelId: string; accountId?: string; conversationId?: string; messageId?: string; isGroup?: boolean; groupId?: string; }): CanonicalSentMessageHookContext { return { to: params.to, content: params.content, success: params.success, error: params.error, channelId: params.channelId, accountId: params.accountId, conversationId: params.conversationId ?? params.to, messageId: params.messageId, isGroup: params.isGroup, groupId: params.groupId, }; } export function toPluginMessageContext( canonical: CanonicalInboundMessageHookContext | CanonicalSentMessageHookContext, ): PluginHookMessageContext { return { channelId: canonical.channelId, accountId: canonical.accountId, conversationId: canonical.conversationId, }; } export function toPluginMessageReceivedEvent( canonical: CanonicalInboundMessageHookContext, ): PluginHookMessageReceivedEvent { return { from: canonical.from, content: canonical.content, timestamp: canonical.timestamp, metadata: { to: canonical.to, provider: canonical.provider, surface: canonical.surface, threadId: canonical.threadId, originatingChannel: canonical.originatingChannel, originatingTo: canonical.originatingTo, messageId: canonical.messageId, senderId: canonical.senderId, senderName: canonical.senderName, senderUsername: canonical.senderUsername, senderE164: canonical.senderE164, guildId: canonical.guildId, channelName: canonical.channelName, }, }; } export function toPluginMessageSentEvent( canonical: CanonicalSentMessageHookContext, ): PluginHookMessageSentEvent { return { to: canonical.to, content: canonical.content, success: canonical.success, ...(canonical.error ? { error: canonical.error } : {}), }; } export function toInternalMessageReceivedContext( canonical: CanonicalInboundMessageHookContext, ): MessageReceivedHookContext { return { from: canonical.from, content: canonical.content, timestamp: canonical.timestamp, channelId: canonical.channelId, accountId: canonical.accountId, conversationId: canonical.conversationId, messageId: canonical.messageId, metadata: { to: canonical.to, provider: canonical.provider, surface: canonical.surface, threadId: canonical.threadId, senderId: canonical.senderId, senderName: canonical.senderName, senderUsername: canonical.senderUsername, senderE164: canonical.senderE164, guildId: canonical.guildId, channelName: canonical.channelName, }, }; } export function toInternalMessageTranscribedContext( canonical: CanonicalInboundMessageHookContext, cfg: OpenClawConfig, ): MessageTranscribedHookContext & { cfg: OpenClawConfig } { const shared = toInternalInboundMessageHookContextBase(canonical); return { ...shared, transcript: canonical.transcript ?? "", cfg, }; } export function toInternalMessagePreprocessedContext( canonical: CanonicalInboundMessageHookContext, cfg: OpenClawConfig, ): MessagePreprocessedHookContext & { cfg: OpenClawConfig } { const shared = toInternalInboundMessageHookContextBase(canonical); return { ...shared, transcript: canonical.transcript, isGroup: canonical.isGroup, groupId: canonical.groupId, cfg, }; } function toInternalInboundMessageHookContextBase(canonical: CanonicalInboundMessageHookContext) { return { from: canonical.from, to: canonical.to, body: canonical.body, bodyForAgent: canonical.bodyForAgent, timestamp: canonical.timestamp, channelId: canonical.channelId, conversationId: canonical.conversationId, messageId: canonical.messageId, senderId: canonical.senderId, senderName: canonical.senderName, senderUsername: canonical.senderUsername, provider: canonical.provider, surface: canonical.surface, mediaPath: canonical.mediaPath, mediaType: canonical.mediaType, }; } export function toInternalMessageSentContext( canonical: CanonicalSentMessageHookContext, ): MessageSentHookContext { return { to: canonical.to, content: canonical.content, success: canonical.success, ...(canonical.error ? { error: canonical.error } : {}), channelId: canonical.channelId, accountId: canonical.accountId, conversationId: canonical.conversationId, messageId: canonical.messageId, ...(canonical.isGroup != null ? { isGroup: canonical.isGroup } : {}), ...(canonical.groupId ? { groupId: canonical.groupId } : {}), }; }