| import { formatInboundEnvelope } from "../../../auto-reply/envelope.js"; |
| import { readSessionUpdatedAt } from "../../../config/sessions.js"; |
| import { logVerbose } from "../../../globals.js"; |
| import type { ResolvedSlackAccount } from "../../accounts.js"; |
| import type { SlackMessageEvent } from "../../types.js"; |
| import type { SlackMonitorContext } from "../context.js"; |
| import { |
| resolveSlackMedia, |
| resolveSlackThreadHistory, |
| type SlackMediaResult, |
| type SlackThreadStarter, |
| } from "../media.js"; |
|
|
| export type SlackThreadContextData = { |
| threadStarterBody: string | undefined; |
| threadHistoryBody: string | undefined; |
| threadSessionPreviousTimestamp: number | undefined; |
| threadLabel: string | undefined; |
| threadStarterMedia: SlackMediaResult[] | null; |
| }; |
|
|
| export async function resolveSlackThreadContextData(params: { |
| ctx: SlackMonitorContext; |
| account: ResolvedSlackAccount; |
| message: SlackMessageEvent; |
| isThreadReply: boolean; |
| threadTs: string | undefined; |
| threadStarter: SlackThreadStarter | null; |
| roomLabel: string; |
| storePath: string; |
| sessionKey: string; |
| envelopeOptions: ReturnType< |
| typeof import("../../../auto-reply/envelope.js").resolveEnvelopeFormatOptions |
| >; |
| effectiveDirectMedia: SlackMediaResult[] | null; |
| }): Promise<SlackThreadContextData> { |
| let threadStarterBody: string | undefined; |
| let threadHistoryBody: string | undefined; |
| let threadSessionPreviousTimestamp: number | undefined; |
| let threadLabel: string | undefined; |
| let threadStarterMedia: SlackMediaResult[] | null = null; |
|
|
| if (!params.isThreadReply || !params.threadTs) { |
| return { |
| threadStarterBody, |
| threadHistoryBody, |
| threadSessionPreviousTimestamp, |
| threadLabel, |
| threadStarterMedia, |
| }; |
| } |
|
|
| const starter = params.threadStarter; |
| if (starter?.text) { |
| threadStarterBody = starter.text; |
| const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80); |
| threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`; |
| if (!params.effectiveDirectMedia && starter.files && starter.files.length > 0) { |
| threadStarterMedia = await resolveSlackMedia({ |
| files: starter.files, |
| token: params.ctx.botToken, |
| maxBytes: params.ctx.mediaMaxBytes, |
| }); |
| if (threadStarterMedia) { |
| const starterPlaceholders = threadStarterMedia.map((item) => item.placeholder).join(", "); |
| logVerbose(`slack: hydrated thread starter file ${starterPlaceholders} from root message`); |
| } |
| } |
| } else { |
| threadLabel = `Slack thread ${params.roomLabel}`; |
| } |
|
|
| const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20; |
| threadSessionPreviousTimestamp = readSessionUpdatedAt({ |
| storePath: params.storePath, |
| sessionKey: params.sessionKey, |
| }); |
|
|
| if (threadInitialHistoryLimit > 0 && !threadSessionPreviousTimestamp) { |
| const threadHistory = await resolveSlackThreadHistory({ |
| channelId: params.message.channel, |
| threadTs: params.threadTs, |
| client: params.ctx.app.client, |
| currentMessageTs: params.message.ts, |
| limit: threadInitialHistoryLimit, |
| }); |
|
|
| if (threadHistory.length > 0) { |
| const uniqueUserIds = [ |
| ...new Set( |
| threadHistory.map((item) => item.userId).filter((id): id is string => Boolean(id)), |
| ), |
| ]; |
| const userMap = new Map<string, { name?: string }>(); |
| await Promise.all( |
| uniqueUserIds.map(async (id) => { |
| const user = await params.ctx.resolveUserName(id); |
| if (user) { |
| userMap.set(id, user); |
| } |
| }), |
| ); |
|
|
| const historyParts: string[] = []; |
| for (const historyMsg of threadHistory) { |
| const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null; |
| const msgSenderName = |
| msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown"); |
| const isBot = Boolean(historyMsg.botId); |
| const role = isBot ? "assistant" : "user"; |
| const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`; |
| historyParts.push( |
| formatInboundEnvelope({ |
| channel: "Slack", |
| from: `${msgSenderName} (${role})`, |
| timestamp: historyMsg.ts ? Math.round(Number(historyMsg.ts) * 1000) : undefined, |
| body: msgWithId, |
| chatType: "channel", |
| envelope: params.envelopeOptions, |
| }), |
| ); |
| } |
| threadHistoryBody = historyParts.join("\n\n"); |
| logVerbose( |
| `slack: populated thread history with ${threadHistory.length} messages for new session`, |
| ); |
| } |
| } |
|
|
| return { |
| threadStarterBody, |
| threadHistoryBody, |
| threadSessionPreviousTimestamp, |
| threadLabel, |
| threadStarterMedia, |
| }; |
| } |
|
|