UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

143 lines 4.55 kB
import { smoothStream, } from "ai"; export const DEFAULT_STREAMING_OPTIONS = { // This chunks by sentences / clauses. Punctuation followed by whitespace. chunking: /[\p{P}\s]/u, throttleMs: 250, }; export function mergeTransforms(options, existing) { if (!options) { return existing; } const chunking = typeof options === "boolean" ? DEFAULT_STREAMING_OPTIONS.chunking : options.chunking; const transforms = Array.isArray(existing) ? existing : existing ? [existing] : []; transforms.push(smoothStream({ delayInMs: null, chunking })); return transforms; } export class DeltaStreamer { component; ctx; metadata; streamId; options; #nextParts = []; #nextOrder; #nextStepOrder; #latestWrite = 0; #ongoingWrite; #cursor = 0; abortController; constructor(component, ctx, options, metadata) { this.component = component; this.ctx = ctx; this.metadata = metadata; this.options = typeof options === "boolean" ? DEFAULT_STREAMING_OPTIONS : { ...DEFAULT_STREAMING_OPTIONS, ...options, }; this.metadata = metadata; this.#nextParts = []; this.#nextOrder = metadata.order ?? 0; this.#nextStepOrder = (metadata.stepOrder ?? 0) + 1; this.abortController = new AbortController(); if (metadata.abortSignal) { metadata.abortSignal.addEventListener("abort", () => { this.abortController.abort(); }); } } async addParts(parts) { if (this.abortController.signal.aborted) { return; } if (!this.streamId) { this.streamId = await this.ctx.runMutation(this.component.streams.create, { ...this.metadata, order: this.#nextOrder, stepOrder: this.#nextStepOrder, }); } this.#nextParts.push(...parts); if (!this.#ongoingWrite && Date.now() - this.#latestWrite >= this.options.throttleMs) { this.#ongoingWrite = this.#sendDelta(); } } async #sendDelta() { if (this.abortController.signal.aborted) { return; } const delta = this.#createDelta(); this.#latestWrite = Date.now(); try { const success = await this.ctx.runMutation(this.component.streams.addDelta, delta); if (!success) { this.abortController.abort(); } } catch (e) { this.abortController.abort(); throw e; } // Now that we've sent the delta, check if we need to send another one. if (this.#nextParts.length > 0 && Date.now() - this.#latestWrite >= this.options.throttleMs) { // We send again immediately with the accumulated deltas. this.#ongoingWrite = this.#sendDelta(); } else { this.#ongoingWrite = undefined; } } #createDelta() { const start = this.#cursor; const end = start + this.#nextParts.length; this.#cursor = end; const parts = this.#nextParts; this.#nextParts = []; if (!this.streamId) { throw new Error("Creating a delta before the stream is created"); } return { streamId: this.streamId, start, end, parts, }; } async finish(messages) { if (this.#ongoingWrite) { await this.#ongoingWrite; this.#ongoingWrite = undefined; } if (!this.streamId) { throw new Error("Finish called before stream is created"); } const lastMessage = messages.at(-1); if (lastMessage) { this.#nextOrder = lastMessage.order; this.#nextStepOrder = lastMessage.stepOrder + 1; } else { console.warn("Step finished without generating a message"); } const finalDelta = this.#nextParts.length > 0 ? this.#createDelta() : undefined; this.#nextParts = []; const streamId = this.streamId; this.streamId = undefined; this.#cursor = 0; await this.ctx.runMutation(this.component.streams.finish, { streamId, finalDelta, }); } } //# sourceMappingURL=streaming.js.map