UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

152 lines (151 loc) • 7.63 kB
import { c as prepareSqliteQuerySync, n as createSqliteQueryCache, r as executeSqliteQuerySync } from "./kysely-sync-K7ftY-g4.mjs"; import { n as sha256Hex } from "./node-crypto-BG4u3UnX.mjs"; import { t as getSessionKysely } from "./session-accessor.sqlite-scope-helpers-DOK3pvYX.mjs"; import { l as readSessionEntryRow } from "./session-accessor.sqlite-entry-read-CWTZjXLR.mjs"; import { n as ConversationDeliveryMissingError, t as ConversationDeliveryInputError } from "./conversation-delivery-store.types-CMzZxTv2.mjs"; //#region src/config/sessions/conversation-delivery-store.kernel.ts function normalizeOperationId(value) { const operationId = value.trim(); if (!operationId) throw new Error("Conversation delivery operation id is required"); return operationId; } function normalizeStatus(value) { switch (value) { case "created": case "queued": case "sent": case "suppressed": case "rejected": case "unknown": case "replied": return value; default: throw new Error(`Invalid conversation delivery status: ${value}`); } } function normalizeOperationKind(value) { if (value === "send" || value === "turn") return value; throw new Error(`Invalid conversation delivery operation kind: ${value}`); } function mapRow(row) { const reply = row.reply_message_id && row.reply_text !== null && row.reply_timestamp !== null ? { messageId: row.reply_message_id, ...row.reply_to_id ? { replyToId: row.reply_to_id } : {}, ...row.reply_thread_id ? { threadId: row.reply_thread_id } : {}, text: row.reply_text, timestamp: row.reply_timestamp } : void 0; return { operationId: row.operation_id, operationKind: normalizeOperationKind(row.operation_kind), conversationRef: row.conversation_id, channel: row.channel, ...row.source_session_key ? { sourceSessionKey: row.source_session_key } : {}, messageHash: row.message_hash, status: normalizeStatus(row.status), ...row.prepared_message_id ? { preparedMessageId: row.prepared_message_id } : {}, ...row.platform_message_id ? { platformMessageId: row.platform_message_id } : {}, ...row.queue_id ? { queueId: row.queue_id } : {}, ...row.rejection_error ? { rejectionError: row.rejection_error } : {}, ...reply ? { reply } : {}, createdAt: row.created_at, updatedAt: row.updated_at }; } function assertConversationDeliveryInput(record, input, messageHash = sha256Hex(input.message)) { if (record.conversationRef !== input.conversationRef || record.operationKind !== input.operationKind || record.sourceSessionKey !== (input.sourceSessionKey?.trim() || void 0) || record.messageHash !== messageHash) throw new ConversationDeliveryInputError(`Conversation delivery operation was reused with different input: ${record.operationId}`); } function createOperationQuery(database) { const db = getSessionKysely(database); return prepareSqliteQuerySync(database, (parameter) => db.selectFrom("conversation_deliveries as delivery").innerJoin("conversations as conversation", "conversation.conversation_id", "delivery.conversation_id").selectAll("delivery").select("conversation.channel as channel").where("delivery.operation_id", "=", parameter((operationId) => operationId))); } const operationQuery = createSqliteQueryCache(createOperationQuery); function selectOperation(database, operationId) { const row = operationQuery(database.db)(operationId).rows[0]; return row ? mapRow(row) : void 0; } function readConversationDeliveryInDatabase(database, lookup) { if (!("operationId" in lookup)) return findConversationTurnDeliveryInDatabase(database, lookup); const { operationId, expectedInput } = lookup; const record = selectOperation(database, normalizeOperationId(operationId)); if (record && expectedInput) assertConversationDeliveryInput(record, expectedInput); return record; } function beginConversationDeliveryInDatabase(database, params) { const operationId = normalizeOperationId(params.operationId); const sourceSessionKey = params.sourceSessionKey?.trim() || void 0; const messageHash = sha256Hex(params.message); const existing = selectOperation(database, operationId); if (existing) { assertConversationDeliveryInput(existing, params, messageHash); return { created: false, record: existing }; } const now = Date.now(); const db = getSessionKysely(database.db); executeSqliteQuerySync(database.db, db.insertInto("conversation_deliveries").values({ operation_id: operationId, operation_kind: params.operationKind, conversation_id: params.conversationRef, source_session_key: sourceSessionKey ?? null, message_hash: messageHash, status: "created", prepared_message_id: params.preparedMessageId ?? null, platform_message_id: null, queue_id: null, rejection_error: null, reply_message_id: null, reply_to_id: null, reply_thread_id: null, reply_text: null, reply_timestamp: null, created_at: now, updated_at: now })); const record = selectOperation(database, operationId); if (!record) throw new Error(`Conversation delivery operation was not persisted: ${operationId}`); return { created: true, record }; } function transitionConversationDeliveryInDatabase(database, params) { if (params.session) { const { sessionKey, sessionId, lifecycleRevision } = params.session; const current = readSessionEntryRow(database, sessionKey)?.entry; if (current?.sessionId !== sessionId || current.lifecycleRevision !== lifecycleRevision) throw new Error(`session changed before captured reply persistence: ${sessionKey}`); } const operationId = normalizeOperationId(params.operationId); const current = selectOperation(database, operationId); if (!current) throw new ConversationDeliveryMissingError(`Conversation delivery operation not found: ${operationId}`); if (!params.allowedFrom.includes(current.status)) return current; const db = getSessionKysely(database.db); executeSqliteQuerySync(database.db, db.updateTable("conversation_deliveries").set({ status: params.status, ...params.queueId !== void 0 ? { queue_id: params.queueId } : {}, ...params.platformMessageId !== void 0 ? { platform_message_id: params.platformMessageId } : {}, ...params.rejectionError !== void 0 ? { rejection_error: params.rejectionError } : {}, ...params.reply ? { reply_message_id: params.reply.messageId, reply_to_id: params.reply.replyToId ?? null, reply_thread_id: params.reply.threadId ?? null, reply_text: params.reply.text, reply_timestamp: params.reply.timestamp } : {}, updated_at: Date.now() }).where("operation_id", "=", operationId)); const record = selectOperation(database, operationId); if (!record) throw new Error(`Conversation delivery operation disappeared: ${operationId}`); return record; } function findConversationTurnDeliveryInDatabase(database, params) { const db = getSessionKysely(database.db); const row = executeSqliteQuerySync(database.db, db.selectFrom("conversation_deliveries as delivery").innerJoin("conversations as conversation", "conversation.conversation_id", "delivery.conversation_id").selectAll("delivery").select("conversation.channel as channel").where("delivery.conversation_id", "=", params.conversationRef).where("delivery.operation_kind", "=", "turn").where((eb) => eb.or([eb("delivery.platform_message_id", "=", params.replyToId), eb("delivery.prepared_message_id", "=", params.replyToId)])).where("delivery.status", "in", [ "queued", "sent", "replied" ]).orderBy("delivery.updated_at", "desc").limit(1)).rows[0]; return row ? mapRow(row) : void 0; } //#endregion export { beginConversationDeliveryInDatabase, readConversationDeliveryInDatabase, transitionConversationDeliveryInDatabase };