import { useEffect, useMemo, useRef } from "react"; import { useExternalStoreRuntime, type ThreadMessage, type AppendMessage, type ExternalStoreAdapter, } from "@assistant-ui/react"; export interface PaperclipIssueRuntimeReassignment { assigneeAgentId: string | null; assigneeUserId: string | null; } export interface PaperclipIssueRuntimeSendOptions { body: string; reopen?: boolean; reassignment?: PaperclipIssueRuntimeReassignment; } interface UsePaperclipIssueRuntimeOptions { messages: readonly ThreadMessage[]; isRunning: boolean; onSend: (options: PaperclipIssueRuntimeSendOptions) => Promise; onCancel?: (() => Promise) | undefined; } function asRecord(value: unknown): Record | null { if (typeof value !== "object" || value === null || Array.isArray(value)) return null; return value as Record; } function readTextContent(message: AppendMessage) { return message.content .filter((part): part is Extract<(typeof message.content)[number], { type: "text" }> => part.type === "text") .map((part) => part.text) .join("") .trim(); } export function usePaperclipIssueRuntime({ messages, isRunning, onSend, onCancel, }: UsePaperclipIssueRuntimeOptions) { const onSendRef = useRef(onSend); const onCancelRef = useRef(onCancel); useEffect(() => { onSendRef.current = onSend; }, [onSend]); useEffect(() => { onCancelRef.current = onCancel; }, [onCancel]); const adapter = useMemo>(() => ({ messages, isRunning, onNew: async (message) => { const body = readTextContent(message); if (!body) return; const custom = asRecord(message.runConfig?.custom); const reassignmentRecord = asRecord(custom?.reassignment); const reassignment = reassignmentRecord && ("assigneeAgentId" in reassignmentRecord || "assigneeUserId" in reassignmentRecord) ? { assigneeAgentId: typeof reassignmentRecord.assigneeAgentId === "string" ? reassignmentRecord.assigneeAgentId : null, assigneeUserId: typeof reassignmentRecord.assigneeUserId === "string" ? reassignmentRecord.assigneeUserId : null, } : undefined; await onSendRef.current({ body, reopen: custom?.reopen === true ? true : undefined, reassignment, }); }, ...(onCancel ? { onCancel: async () => { await onCancelRef.current?.(); }, } : {}), }), [messages, isRunning, !!onCancel]); return useExternalStoreRuntime(adapter); }