openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
152 lines (151 loc) • 7.63 kB
JavaScript
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 };