@convex-dev/agent
Version:
A agent component for Convex.
143 lines • 4.55 kB
JavaScript
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