import type { MessageBlock } from "@rakazo/contracts"; import type { Prisma, PrismaClient } from "./client.js"; /** Group turns use channel inputs and their own outputs, never private thread history. */ export function loadRunHistoryMessages( prisma: PrismaClient, run: { id: string; threadId: string }, limit: number, channelId?: string, ) { return prisma.message.findMany({ where: { threadId: run.threadId, ...(channelId ? { OR: [ { role: "user", blocks: { array_contains: [{ kind: "channel_message", channelId }] }, }, { role: "bot", runId: run.id }, ], } : {}), }, orderBy: { seq: "desc" }, take: limit, select: { id: true, threadId: true, seq: true, role: true, runId: true, blocks: true, replyToMessageId: true, replyQuote: true, replyTo: { select: { id: true, threadId: true, role: true, blocks: true } }, }, }); } export interface CreateThreadMessageInput { threadId: string; role: "user" | "bot" | "system"; blocks: MessageBlock[]; botId?: string; replyToMessageId?: string; replyQuote?: string; runId?: string; clientNonce?: string; markUnread?: boolean; } export async function createThreadMessage(prisma: PrismaClient, input: CreateThreadMessageInput) { return prisma.$transaction((tx: Prisma.TransactionClient) => createThreadMessageInTransaction(tx, input), ); } export async function createThreadMessageInTransaction( tx: Prisma.TransactionClient, input: CreateThreadMessageInput, ) { const thread = await tx.thread.update({ where: { id: input.threadId }, data: { nextMessageSeq: { increment: 1 }, unread: (input.markUnread ?? input.role === "bot") ? true : undefined, }, select: { nextMessageSeq: true }, }); await assertRunCanWriteHistory(tx, input.runId); return tx.message.create({ data: { threadId: input.threadId, seq: thread.nextMessageSeq - 1, role: input.role, blocks: input.blocks as Prisma.InputJsonValue, botId: input.botId, replyToMessageId: input.replyToMessageId, replyQuote: input.replyQuote, runId: input.runId, clientNonce: input.clientNonce, }, }); } export class RunHistoryWriteError extends Error { constructor() { super("Run cannot write thread history"); this.name = "RunHistoryWriteError"; } } export async function assertRunCanWriteHistory( tx: Prisma.TransactionClient, runId?: string, ): Promise<{ status: string; startedAt: Date | null } | undefined> { if (!runId) return; const run = await tx.run.findUnique({ where: { id: runId }, select: { status: true, startedAt: true }, }); if (!run || run.status === "cancelled") { throw new RunHistoryWriteError(); } return run; }