UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

101 lines 4.26 kB
"use client"; import { sorted } from "../shared.js"; import { useQuery } from "convex/react"; import { useState } from "react"; import { assert } from "convex-helpers"; export function useDeltaStreams(query, args, options) { // We hold onto and modify state directly to avoid re-running unnecessarily. const [state] = useState({ startOrder: options?.startOrder ?? 0, deltaStreams: undefined, threadId: args === "skip" ? undefined : args.threadId, }); const [cursors, setCursors] = useState({}); if (args !== "skip" && state.threadId !== args.threadId) { state.threadId = args.threadId; state.deltaStreams = undefined; state.startOrder = options?.startOrder ?? 0; setCursors({}); } if (state.deltaStreams?.length || (options?.startOrder && options.startOrder < state.startOrder)) { const cacheFriendlyStartOrder = options?.startOrder ? // round down to the nearest 10 for some cache benefits options.startOrder - (options.startOrder % 10) : 0; if (cacheFriendlyStartOrder !== state.startOrder) { state.startOrder = cacheFriendlyStartOrder; } } // Get all the active streams const streamList = useQuery(query, args === "skip" ? args : { ...args, streamArgs: { kind: "list", startOrder: state.startOrder, }, }); const streamMessages = args === "skip" ? undefined : !streamList ? state.deltaStreams?.map(({ streamMessage }) => streamMessage) : sorted(streamList.streams.messages.filter(({ streamId, order }) => !options?.skipStreamIds?.includes(streamId) && (!options?.startOrder || order >= options.startOrder))); // Get the deltas for all the active streams, if any. const cursorQuery = useQuery(query, args === "skip" || !streamMessages?.length ? "skip" : { ...args, streamArgs: { kind: "deltas", cursors: streamMessages.map(({ streamId }) => ({ streamId, cursor: cursors[streamId] ?? 0, })), }, }); const newDeltas = cursorQuery?.streams.deltas; if (newDeltas?.length && streamMessages) { const newDeltasByStreamId = new Map(); for (const delta of newDeltas) { const oldCursor = cursors[delta.streamId]; if (oldCursor && delta.start < oldCursor) continue; const existing = newDeltasByStreamId.get(delta.streamId); if (existing) { const previousEnd = existing.at(-1).end; assert(previousEnd === delta.start, `Gap found in deltas for ${delta.streamId} jumping to ${delta.start} from ${previousEnd}`); existing.push(delta); } else { assert(!oldCursor || oldCursor === delta.start, `Gap found - first delta after ${oldCursor} is ${delta.start} for stream ${delta.streamId}`); newDeltasByStreamId.set(delta.streamId, [delta]); } } const newCursors = {}; for (const { streamId } of streamMessages) { const cursor = newDeltasByStreamId.get(streamId)?.at(-1)?.end ?? cursors[streamId]; if (cursor !== undefined) { newCursors[streamId] = cursor; } } setCursors(newCursors); // we defensively create a new object so object identity matches contents state.deltaStreams = streamMessages.map((streamMessage) => { const streamId = streamMessage.streamId; const old = state.deltaStreams?.find((ds) => ds.streamMessage.streamId === streamId); const newDeltas = newDeltasByStreamId.get(streamId); if (!newDeltas && streamMessage === old?.streamMessage) { return old; } return { streamMessage, deltas: [...(old?.deltas ?? []), ...(newDeltas ?? [])], }; }); } return state.deltaStreams; } //# sourceMappingURL=useDeltaStreams.js.map