UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

137 lines (136 loc) 6.25 kB
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js"; import { i as stripInlineDirectiveTagsForDelivery } from "./directive-tags-Wp1S72ap.js"; import { i as isSessionBoundaryCommandText } from "./command-detection-DoshyUmB.js"; import { i as readRecentUserAssistantTextForSession } from "./transcript-Dv8-hmIo.js"; //#region src/channels/inbound-event/session-transcript-context.runtime.ts function messageKeys(message) { const id = typeof message.message_id === "string" ? message.message_id.trim() : ""; const body = typeof message.body === "string" ? message.body.trim() : ""; const timestamp = message.timestamp_ms; return [id ? `id:${id}` : "", body && typeof timestamp === "number" ? `text:${timestamp}:${body}` : ""].filter(Boolean); } function historyMessage(entry) { return { ...entry.messageId ? { message_id: entry.messageId } : {}, sender: entry.sender, ...entry.timestamp !== void 0 ? { timestamp_ms: entry.timestamp } : {}, body: entry.body }; } function compareMessages(left, right) { return (typeof left.timestamp_ms === "number" ? left.timestamp_ms : 0) - (typeof right.timestamp_ms === "number" ? right.timestamp_ms : 0); } function mergeMessages(params) { const seen = new Set(params.existing.flatMap(messageKeys)); const seenTranscript = /* @__PURE__ */ new Set(); const added = []; for (const item of params.transcript) { if (item.transcriptId && params.dedupeTranscriptIds.has(item.transcriptId)) continue; const message = historyMessage(item.entry); const keys = messageKeys(message); const assistantTextKeys = item.role === "assistant" ? messageKeys({ ...message, body: stripInlineDirectiveTagsForDelivery(item.entry.body).text }) : keys; if (keys.some((key) => seenTranscript.has(key)) || item.role === "assistant" && assistantTextKeys.some((key) => params.dedupeAssistantTextKeys.has(key)) || item.role === "user" && keys.some((key) => seen.has(key))) continue; keys.forEach((key) => seenTranscript.add(key)); added.push(message); } const sorted = [...added, ...params.existing].toSorted(compareMessages); if (sorted.length <= params.limit) return { added: added.length, messages: sorted }; const pinned = sorted.filter((message) => message.is_reply_target === true); const available = Math.max(0, params.limit - pinned.length); const recent = available === 0 ? [] : sorted.filter((message) => message.is_reply_target !== true).slice(-available); return { added: added.length, messages: [...pinned.slice(-params.limit), ...recent].toSorted(compareMessages) }; } function chatWindowEntries(ctx) { return (ctx.ChannelStructuredContext ?? []).filter((entry) => entry.type === "chat_window" && Boolean(entry.payload) && typeof entry.payload === "object" && !Array.isArray(entry.payload)); } /** Merges active canonical transcript turns into the prepared channel history in place. */ async function mergeSessionTranscriptContext(params) { const options = params.ctx.SessionTranscriptContext; const limit = Math.max(0, Math.floor(options?.historyLimit ?? 0)); if (limit === 0 || isSessionBoundaryCommandText(params.ctx.CommandBody ?? params.ctx.RawBody, { botUsername: params.ctx.BotUsername })) return; const agentId = params.agentId?.trim() || parseAgentSessionKey(params.sessionKey)?.agentId; if (!agentId) throw new Error("Session transcript context requires an agent owner."); const windows = chatWindowEntries(params.ctx); const turns = await readRecentUserAssistantTextForSession({ agentId, sessionKey: params.sessionKey, storePath: params.storePath, limit, ...windows.length === 0 && options?.chatWindow === true ? { includeCronDirectDeliveryContext: true } : {}, ...(options?.beforeTimestampMs ?? params.ctx.Timestamp) !== void 0 ? { beforeTimestampMs: options?.beforeTimestampMs ?? params.ctx.Timestamp } : {}, ...options?.minTimestampMs !== void 0 ? { minTimestampMs: options.minTimestampMs } : {} }); const labels = options?.senderLabels ?? { assistant: "Assistant", user: "User" }; const transcript = turns.map((turn) => { const item = { entry: { sender: `${labels[turn.role]}${turn.sourceChannel ? ` (${turn.sourceChannel})` : ""}`, body: turn.text }, role: turn.role }; if (turn.id) { item.transcriptId = turn.id; item.entry.messageId = `session:${turn.id}`; } if (turn.timestamp !== void 0) item.entry.timestamp = turn.timestamp; return item; }); if (transcript.length === 0) return; if (windows.length === 0 && options?.chatWindow) { params.ctx.ChannelStructuredContext = [...params.ctx.ChannelStructuredContext ?? [], { label: "Conversation context", source: "session", type: "chat_window", payload: { order: "chronological", relation: "selected_for_current_message", messages: transcript.map(({ entry }) => historyMessage(entry)).slice(-limit) } }]; return; } if (windows.length === 0) { const existing = params.ctx.InboundHistory ?? []; const seen = new Set(existing.flatMap((entry) => messageKeys(historyMessage(entry)))); const added = transcript.flatMap(({ entry }) => { const keys = messageKeys(historyMessage(entry)); if (keys.some((key) => seen.has(key))) return []; keys.forEach((key) => seen.add(key)); return [entry]; }); params.ctx.InboundHistory = [...existing, ...added].toSorted((left, right) => (left.timestamp ?? 0) - (right.timestamp ?? 0)).slice(-limit); return; } for (const window of windows) { const existing = Array.isArray(window.payload.messages) ? window.payload.messages.filter((message) => Boolean(message) && typeof message === "object" && !Array.isArray(message)) : []; const dedupeIds = window.sessionTranscriptDedupeMessageIds; const assistantTextKeys = window.sessionTranscriptAssistantTextDedupeKeys; const merged = mergeMessages({ existing, transcript, dedupeTranscriptIds: Array.isArray(dedupeIds) ? new Set(dedupeIds.filter((id) => typeof id === "string")) : /* @__PURE__ */ new Set(), dedupeAssistantTextKeys: new Set(assistantTextKeys ?? []), limit }); window.payload = { ...window.payload, messages: merged.messages }; if (merged.added > 0) window.source = "session"; } } //#endregion export { mergeSessionTranscriptContext };