| import { normalizeCommandBody } from "../auto-reply/commands-registry.js"; |
| import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../auto-reply/envelope.js"; |
| import { |
| buildPendingHistoryContextFromMap, |
| type HistoryEntry, |
| } from "../auto-reply/reply/history.js"; |
| import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js"; |
| import { toLocationContext } from "../channels/location.js"; |
| import { recordInboundSession } from "../channels/session.js"; |
| import type { OpenClawConfig } from "../config/config.js"; |
| import { readSessionUpdatedAt, resolveStorePath } from "../config/sessions.js"; |
| import type { |
| TelegramDirectConfig, |
| TelegramGroupConfig, |
| TelegramTopicConfig, |
| } from "../config/types.js"; |
| import { logVerbose, shouldLogVerbose } from "../globals.js"; |
| import type { ResolvedAgentRoute } from "../routing/resolve-route.js"; |
| import { resolveInboundLastRouteSessionKey } from "../routing/resolve-route.js"; |
| import { resolvePinnedMainDmOwnerFromAllowlist } from "../security/dm-policy-shared.js"; |
| import { normalizeAllowFrom } from "./bot-access.js"; |
| import type { |
| TelegramMediaRef, |
| TelegramMessageContextOptions, |
| } from "./bot-message-context.types.js"; |
| import { |
| buildGroupLabel, |
| buildSenderLabel, |
| buildSenderName, |
| buildTelegramGroupFrom, |
| describeReplyTarget, |
| normalizeForwardedContext, |
| type TelegramThreadSpec, |
| } from "./bot/helpers.js"; |
| import type { TelegramContext } from "./bot/types.js"; |
| import { resolveTelegramGroupPromptSettings } from "./group-config-helpers.js"; |
|
|
| export async function buildTelegramInboundContextPayload(params: { |
| cfg: OpenClawConfig; |
| primaryCtx: TelegramContext; |
| msg: TelegramContext["message"]; |
| allMedia: TelegramMediaRef[]; |
| replyMedia: TelegramMediaRef[]; |
| isGroup: boolean; |
| isForum: boolean; |
| chatId: number | string; |
| senderId: string; |
| senderUsername: string; |
| resolvedThreadId?: number; |
| dmThreadId?: number; |
| threadSpec: TelegramThreadSpec; |
| route: ResolvedAgentRoute; |
| rawBody: string; |
| bodyText: string; |
| historyKey?: string; |
| historyLimit: number; |
| groupHistories: Map<string, HistoryEntry[]>; |
| groupConfig?: TelegramGroupConfig | TelegramDirectConfig; |
| topicConfig?: TelegramTopicConfig; |
| stickerCacheHit: boolean; |
| effectiveWasMentioned: boolean; |
| commandAuthorized: boolean; |
| locationData?: import("../channels/location.js").NormalizedLocation; |
| options?: TelegramMessageContextOptions; |
| dmAllowFrom?: Array<string | number>; |
| }): Promise<{ |
| ctxPayload: ReturnType<typeof finalizeInboundContext>; |
| skillFilter: string[] | undefined; |
| }> { |
| const { |
| cfg, |
| primaryCtx, |
| msg, |
| allMedia, |
| replyMedia, |
| isGroup, |
| isForum, |
| chatId, |
| senderId, |
| senderUsername, |
| resolvedThreadId, |
| dmThreadId, |
| threadSpec, |
| route, |
| rawBody, |
| bodyText, |
| historyKey, |
| historyLimit, |
| groupHistories, |
| groupConfig, |
| topicConfig, |
| stickerCacheHit, |
| effectiveWasMentioned, |
| commandAuthorized, |
| locationData, |
| options, |
| dmAllowFrom, |
| } = params; |
| const replyTarget = describeReplyTarget(msg); |
| const forwardOrigin = normalizeForwardedContext(msg); |
| const replyForwardAnnotation = replyTarget?.forwardedFrom |
| ? `[Forwarded from ${replyTarget.forwardedFrom.from}${ |
| replyTarget.forwardedFrom.date |
| ? ` at ${new Date(replyTarget.forwardedFrom.date * 1000).toISOString()}` |
| : "" |
| }]\n` |
| : ""; |
| const replySuffix = replyTarget |
| ? replyTarget.kind === "quote" |
| ? `\n\n[Quoting ${replyTarget.sender}${ |
| replyTarget.id ? ` id:${replyTarget.id}` : "" |
| }]\n${replyForwardAnnotation}"${replyTarget.body}"\n[/Quoting]` |
| : `\n\n[Replying to ${replyTarget.sender}${ |
| replyTarget.id ? ` id:${replyTarget.id}` : "" |
| }]\n${replyForwardAnnotation}${replyTarget.body}\n[/Replying]` |
| : ""; |
| const forwardPrefix = forwardOrigin |
| ? `[Forwarded from ${forwardOrigin.from}${ |
| forwardOrigin.date ? ` at ${new Date(forwardOrigin.date * 1000).toISOString()}` : "" |
| }]\n` |
| : ""; |
| const groupLabel = isGroup ? buildGroupLabel(msg, chatId, resolvedThreadId) : undefined; |
| const senderName = buildSenderName(msg); |
| const conversationLabel = isGroup |
| ? (groupLabel ?? `group:${chatId}`) |
| : buildSenderLabel(msg, senderId || chatId); |
| const storePath = resolveStorePath(cfg.session?.store, { |
| agentId: route.agentId, |
| }); |
| const envelopeOptions = resolveEnvelopeFormatOptions(cfg); |
| const previousTimestamp = readSessionUpdatedAt({ |
| storePath, |
| sessionKey: route.sessionKey, |
| }); |
| const body = formatInboundEnvelope({ |
| channel: "Telegram", |
| from: conversationLabel, |
| timestamp: msg.date ? msg.date * 1000 : undefined, |
| body: `${forwardPrefix}${bodyText}${replySuffix}`, |
| chatType: isGroup ? "group" : "direct", |
| sender: { |
| name: senderName, |
| username: senderUsername || undefined, |
| id: senderId || undefined, |
| }, |
| previousTimestamp, |
| envelope: envelopeOptions, |
| }); |
| let combinedBody = body; |
| if (isGroup && historyKey && historyLimit > 0) { |
| combinedBody = buildPendingHistoryContextFromMap({ |
| historyMap: groupHistories, |
| historyKey, |
| limit: historyLimit, |
| currentMessage: combinedBody, |
| formatEntry: (entry) => |
| formatInboundEnvelope({ |
| channel: "Telegram", |
| from: groupLabel ?? `group:${chatId}`, |
| timestamp: entry.timestamp, |
| body: `${entry.body} [id:${entry.messageId ?? "unknown"} chat:${chatId}]`, |
| chatType: "group", |
| senderLabel: entry.sender, |
| envelope: envelopeOptions, |
| }), |
| }); |
| } |
|
|
| const { skillFilter, groupSystemPrompt } = resolveTelegramGroupPromptSettings({ |
| groupConfig, |
| topicConfig, |
| }); |
| const commandBody = normalizeCommandBody(rawBody, { |
| botUsername: primaryCtx.me?.username?.toLowerCase(), |
| }); |
| const inboundHistory = |
| isGroup && historyKey && historyLimit > 0 |
| ? (groupHistories.get(historyKey) ?? []).map((entry) => ({ |
| sender: entry.sender, |
| body: entry.body, |
| timestamp: entry.timestamp, |
| })) |
| : undefined; |
| const currentMediaForContext = stickerCacheHit ? [] : allMedia; |
| const contextMedia = [...currentMediaForContext, ...replyMedia]; |
| const ctxPayload = finalizeInboundContext({ |
| Body: combinedBody, |
| BodyForAgent: bodyText, |
| InboundHistory: inboundHistory, |
| RawBody: rawBody, |
| CommandBody: commandBody, |
| From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`, |
| To: `telegram:${chatId}`, |
| SessionKey: route.sessionKey, |
| AccountId: route.accountId, |
| ChatType: isGroup ? "group" : "direct", |
| ConversationLabel: conversationLabel, |
| GroupSubject: isGroup ? (msg.chat.title ?? undefined) : undefined, |
| GroupSystemPrompt: isGroup || (!isGroup && groupConfig) ? groupSystemPrompt : undefined, |
| SenderName: senderName, |
| SenderId: senderId || undefined, |
| SenderUsername: senderUsername || undefined, |
| Provider: "telegram", |
| Surface: "telegram", |
| BotUsername: primaryCtx.me?.username ?? undefined, |
| MessageSid: options?.messageIdOverride ?? String(msg.message_id), |
| ReplyToId: replyTarget?.id, |
| ReplyToBody: replyTarget?.body, |
| ReplyToSender: replyTarget?.sender, |
| ReplyToIsQuote: replyTarget?.kind === "quote" ? true : undefined, |
| ReplyToForwardedFrom: replyTarget?.forwardedFrom?.from, |
| ReplyToForwardedFromType: replyTarget?.forwardedFrom?.fromType, |
| ReplyToForwardedFromId: replyTarget?.forwardedFrom?.fromId, |
| ReplyToForwardedFromUsername: replyTarget?.forwardedFrom?.fromUsername, |
| ReplyToForwardedFromTitle: replyTarget?.forwardedFrom?.fromTitle, |
| ReplyToForwardedDate: replyTarget?.forwardedFrom?.date |
| ? replyTarget.forwardedFrom.date * 1000 |
| : undefined, |
| ForwardedFrom: forwardOrigin?.from, |
| ForwardedFromType: forwardOrigin?.fromType, |
| ForwardedFromId: forwardOrigin?.fromId, |
| ForwardedFromUsername: forwardOrigin?.fromUsername, |
| ForwardedFromTitle: forwardOrigin?.fromTitle, |
| ForwardedFromSignature: forwardOrigin?.fromSignature, |
| ForwardedFromChatType: forwardOrigin?.fromChatType, |
| ForwardedFromMessageId: forwardOrigin?.fromMessageId, |
| ForwardedDate: forwardOrigin?.date ? forwardOrigin.date * 1000 : undefined, |
| Timestamp: msg.date ? msg.date * 1000 : undefined, |
| WasMentioned: isGroup ? effectiveWasMentioned : undefined, |
| MediaPath: contextMedia.length > 0 ? contextMedia[0]?.path : undefined, |
| MediaType: contextMedia.length > 0 ? contextMedia[0]?.contentType : undefined, |
| MediaUrl: contextMedia.length > 0 ? contextMedia[0]?.path : undefined, |
| MediaPaths: contextMedia.length > 0 ? contextMedia.map((m) => m.path) : undefined, |
| MediaUrls: contextMedia.length > 0 ? contextMedia.map((m) => m.path) : undefined, |
| MediaTypes: |
| contextMedia.length > 0 |
| ? (contextMedia.map((m) => m.contentType).filter(Boolean) as string[]) |
| : undefined, |
| Sticker: allMedia[0]?.stickerMetadata, |
| StickerMediaIncluded: allMedia[0]?.stickerMetadata ? !stickerCacheHit : undefined, |
| ...(locationData ? toLocationContext(locationData) : undefined), |
| CommandAuthorized: commandAuthorized, |
| MessageThreadId: threadSpec.id, |
| IsForum: isForum, |
| OriginatingChannel: "telegram" as const, |
| OriginatingTo: `telegram:${chatId}`, |
| }); |
|
|
| const pinnedMainDmOwner = !isGroup |
| ? resolvePinnedMainDmOwnerFromAllowlist({ |
| dmScope: cfg.session?.dmScope, |
| allowFrom: dmAllowFrom, |
| normalizeEntry: (entry) => normalizeAllowFrom([entry]).entries[0], |
| }) |
| : null; |
| const updateLastRouteSessionKey = resolveInboundLastRouteSessionKey({ |
| route, |
| sessionKey: route.sessionKey, |
| }); |
|
|
| await recordInboundSession({ |
| storePath, |
| sessionKey: ctxPayload.SessionKey ?? route.sessionKey, |
| ctx: ctxPayload, |
| updateLastRoute: !isGroup |
| ? { |
| sessionKey: updateLastRouteSessionKey, |
| channel: "telegram", |
| to: `telegram:${chatId}`, |
| accountId: route.accountId, |
| threadId: dmThreadId != null ? String(dmThreadId) : undefined, |
| mainDmOwnerPin: |
| updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && senderId |
| ? { |
| ownerRecipient: pinnedMainDmOwner, |
| senderRecipient: senderId, |
| onSkip: ({ ownerRecipient, senderRecipient }) => { |
| logVerbose( |
| `telegram: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`, |
| ); |
| }, |
| } |
| : undefined, |
| } |
| : undefined, |
| onRecordError: (err) => { |
| logVerbose(`telegram: failed updating session meta: ${String(err)}`); |
| }, |
| }); |
|
|
| if (replyTarget && shouldLogVerbose()) { |
| const preview = replyTarget.body.replace(/\s+/g, " ").slice(0, 120); |
| logVerbose( |
| `telegram reply-context: replyToId=${replyTarget.id} replyToSender=${replyTarget.sender} replyToBody="${preview}"`, |
| ); |
| } |
|
|
| if (forwardOrigin && shouldLogVerbose()) { |
| logVerbose( |
| `telegram forward-context: forwardedFrom="${forwardOrigin.from}" type=${forwardOrigin.fromType}`, |
| ); |
| } |
|
|
| if (shouldLogVerbose()) { |
| const preview = body.slice(0, 200).replace(/\n/g, "\\n"); |
| const mediaInfo = allMedia.length > 1 ? ` mediaCount=${allMedia.length}` : ""; |
| const topicInfo = resolvedThreadId != null ? ` topic=${resolvedThreadId}` : ""; |
| logVerbose( |
| `telegram inbound: chatId=${chatId} from=${ctxPayload.From} len=${body.length}${mediaInfo}${topicInfo} preview="${preview}"`, |
| ); |
| } |
|
|
| return { |
| ctxPayload, |
| skillFilter, |
| }; |
| } |
|
|