Bots can offer an exact tool call as a card. Nothing runs until the user taps Accept; the call then runs as its own approved run of the bot it was meant for, and the card shows the outcome. Open suggestions also wait in the inbox and expire after a week. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com>
38 lines
1.8 KiB
TypeScript
38 lines
1.8 KiB
TypeScript
import type { CardBlock, MessageBlock } from "@rakazo/contracts";
|
|
import type { Prisma } from "./client.js";
|
|
import { appendEventInTransaction } from "./events.js";
|
|
|
|
/**
|
|
* Change one card in a bot message and tell the thread. The thread row is locked so two taps on
|
|
* the same card apply one after the other. `change` returns null to leave the card as it is;
|
|
* the result is null when the message, the card or a change is missing.
|
|
*/
|
|
export async function updateCardInTransaction(
|
|
tx: Prisma.TransactionClient,
|
|
input: { threadId: string; messageId: string; cardId: string },
|
|
change: (card: CardBlock) => CardBlock | null,
|
|
): Promise<{ seq: number; card: CardBlock } | null> {
|
|
await tx.$queryRaw`SELECT id FROM threads WHERE id = ${input.threadId} FOR UPDATE`;
|
|
const message = await tx.message.findFirst({
|
|
where: { id: input.messageId, threadId: input.threadId, role: "bot" },
|
|
select: { id: true, blocks: true, botId: true, thread: { select: { spaceId: true } } },
|
|
});
|
|
if (!message?.botId) return null;
|
|
const blocks = (Array.isArray(message.blocks) ? message.blocks : []) as MessageBlock[];
|
|
const index = blocks.findIndex((block) => block.kind === "card" && block.id === input.cardId);
|
|
const card = blocks[index];
|
|
if (card?.kind !== "card") return null;
|
|
const next = change(card);
|
|
if (!next) return null;
|
|
const updated = blocks.map((block, position) => (position === index ? next : block));
|
|
await tx.message.update({ where: { id: message.id }, data: { blocks: updated } });
|
|
const event = await appendEventInTransaction(tx, {
|
|
spaceId: message.thread.spaceId,
|
|
threadId: input.threadId,
|
|
botId: message.botId,
|
|
type: "thread.message.updated",
|
|
payload: { messageId: message.id, role: "bot", blocks: updated },
|
|
});
|
|
return { seq: event.seq, card: next };
|
|
}
|