UNPKG

@agentica/core

Version:

Agentic AI Library specialized in LLM Function Calling

521 lines (424 loc) 15.1 kB
import { streamDefaultReaderToAsyncGenerator, StreamUtil, toAsyncGenerator } from "./StreamUtil"; // Helper function to create a stream with numbers from start to end function createNumberStream( start: number, end: number, ): ReadableStream<number> { return new ReadableStream<number>({ start(controller) { for (let i = start; i <= end; i++) { controller.enqueue(i); } controller.close(); }, }); } // Helper function to create an empty stream function createEmptyStream<T>(): ReadableStream<T> { return new ReadableStream<T>({ start(controller) { controller.close(); }, }); } // Helper function to convert ReadableStream to array async function streamToArray<T>(stream: ReadableStream<T>): Promise<T[]> { const reader = stream.getReader(); const result: T[] = []; while (true) { const { done, value } = await reader.read(); if (done) { break; } result.push(value); } return result; } // Helper function to create test stream function createTestStream<T>(items: T[]): ReadableStream<T> { return new ReadableStream<T>({ start(controller) { for (const item of items) { controller.enqueue(item); } controller.close(); }, }); } // Delay function (for simulating async operations) async function delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } // Helper function to create a stream with numbers from start to end asynchronously async function createDelayedNumberStream( start: number, end: number, delayMs: number, ): Promise<ReadableStream<number>> { // Simulate async work await delay(delayMs); return new ReadableStream<number>({ start(controller) { for (let i = start; i <= end; i++) { controller.enqueue(i); } controller.close(); }, }); } // Helper function to create an empty stream asynchronously async function createEmptyDelayedStream<T>( delayMs: number, ): Promise<ReadableStream<T>> { // Simulate async work await delay(delayMs); return new ReadableStream<T>({ start(controller) { controller.close(); }, }); } // Helper function to create a stream with items having various delay times async function createVariableDelayedNumberStream( items: Array<{ value: number; delay: number }>, ): Promise<ReadableStream<number>> { // Wait for all items to be ready based on their delays await Promise.all(items.map(async item => delay(item.delay))); return new ReadableStream<number>({ start(controller) { for (const item of items) { controller.enqueue(item.value); } controller.close(); }, }); } describe("streamUtil", () => { describe("readAll", () => { it("should read all values from a stream", async () => { const stream = createNumberStream(1, 5); const result = await StreamUtil.readAll(stream); expect(result).toEqual([1, 2, 3, 4, 5]); }); it("should return empty array for empty stream", async () => { const stream = createEmptyStream<number>(); const result = await StreamUtil.readAll(stream); expect(result).toEqual([]); }); it("should handle error in stream", async () => { const stream = new ReadableStream<number>({ start(controller) { controller.enqueue(1); controller.enqueue(2); controller.error(new Error("Stream error")); }, }); await expect(StreamUtil.readAll(stream)).rejects.toThrow("Stream error"); }); it("should handle null or undefined values in stream", async () => { const stream = createTestStream<number | null | undefined>([1, null, 3, undefined, 5]); const result = await StreamUtil.readAll(stream); expect(result).toEqual([1, null, 3, undefined, 5]); }); }); describe("reduce", () => { it("should concatenate strings from number stream", async () => { const stringStream = createNumberStream(1, 3); const stringResult = await StreamUtil.reduce<number, string>( stringStream, (acc, cur) => acc + cur.toString(), "", ); expect(stringResult).toBe("123"); }); it("should sum numbers from stream", async () => { const sumStream = createNumberStream(1, 5); const sumResult = await StreamUtil.reduce<number, number>( sumStream, (acc, cur) => acc + cur, 0, ); expect(sumResult).toBe(15); }); it("should work without initial value", async () => { const noInitialStream = createNumberStream(1, 4); const noInitialResult = await StreamUtil.reduce<number>( noInitialStream, (acc, cur) => acc + cur, ); expect(noInitialResult).toBe(10); }); it("should return initial value for empty stream", async () => { const emptyStream = createEmptyStream<number>(); const emptyResult = await StreamUtil.reduce<number, string>( emptyStream, (acc, cur) => acc + cur.toString(), "initial value", ); expect(emptyResult).toBe("initial value"); }); it("should return null for empty stream without initial value", async () => { const emptyNoInitialStream = createEmptyStream<number>(); const emptyNoInitialResult = await StreamUtil.reduce<number>( emptyNoInitialStream, (acc, cur) => acc + cur, ); expect(emptyNoInitialResult).toBeNull(); }); it("should work with async generated stream", async () => { const stringStream = await createDelayedNumberStream(1, 3, 10); const stringResult = await StreamUtil.reduce<number, string>( stringStream, (acc, cur) => acc + cur.toString(), "", ); expect(stringResult).toBe("123"); }); it("should work with async stream without initial value", async () => { const noInitialStream = await createDelayedNumberStream(1, 4, 15); const noInitialResult = await StreamUtil.reduce<number>( noInitialStream, (acc, cur) => acc + cur, ); expect(noInitialResult).toBe(10); }); it("should work with async stream transformation and aggregation into array", async () => { const transformStream = await createDelayedNumberStream(1, 3, 10); const transformResult = await StreamUtil.reduce<number, string[]>( transformStream, (acc: string[] | number, cur: number): string[] => { if (typeof acc === "number") { // Handle case when no initial value is provided return [`item${acc}`, `item${cur}`]; } return [...acc, `item${cur}`]; }, [], ); expect(transformResult).toEqual(["item1", "item2", "item3"]); }); it("should return initial value for async generated empty stream", async () => { const emptyStream = await createEmptyDelayedStream<number>(30); const emptyResult = await StreamUtil.reduce<number, string>( emptyStream, (acc, cur) => acc + cur.toString(), "initial", ); expect(emptyResult).toBe("initial"); }); it("should work with stream with values created with various async delays", async () => { const delayStream = await createVariableDelayedNumberStream([ { value: 1, delay: 20 }, { value: 2, delay: 40 }, { value: 3, delay: 10 }, ]); const delayResult = await StreamUtil.reduce<number, number>( delayStream, (acc, cur) => acc + cur, 0, ); expect(delayResult).toBe(6); }); it("should handle error in reducer function", async () => { const stream = createNumberStream(1, 3); await expect( StreamUtil.reduce<number, number>( stream, (acc, cur) => { if (cur === 2) { throw new Error("Test error"); } return acc + cur; }, 0, ), ).rejects.toThrow("Test error"); }); it("should handle null or undefined values in stream", async () => { const stream = createTestStream<number | null | undefined>([1, null, 3, undefined, 5]); const result = await StreamUtil.reduce<number | null | undefined, number>( stream, (acc, cur) => (acc ?? 0) + (cur ?? 0), 0, ); expect(result).toBe(9); // 1 + 0 + 3 + 0 + 5 = 9 }); }); describe("from", () => { it("should create a stream with a single value", async () => { const stream = StreamUtil.from("Hello, world!"); const reader = stream.getReader(); const { done, value } = await reader.read(); expect(done).toBe(false); expect(value).toBe("Hello, world!"); const next = await reader.read(); expect(next.done).toBe(true); }); it("should handle null or undefined values", async () => { const stream = StreamUtil.from(null); const reader = stream.getReader(); const { done, value } = await reader.read(); expect(done).toBe(false); expect(value).toBeNull(); const next = await reader.read(); expect(next.done).toBe(true); }); }); describe("transform", () => { it("should transform number stream by doubling", async () => { const numbersInput = [1, 2, 3, 4, 5]; const numberStream = createTestStream(numbersInput); const doubledStream = StreamUtil.transform( numberStream, (num: number) => num * 2, ); const doubledResult = await streamToArray(doubledStream); const expectedDoubled = numbersInput.map(n => n * 2); expect(doubledResult).toEqual(expectedDoubled); }); it("should transform object stream", async () => { const objectsInput = [ { name: "item1", value: 10 }, { name: "item2", value: 20 }, { name: "item3", value: 30 }, ]; const objectStream = createTestStream(objectsInput); const transformedObjectStream = StreamUtil.transform(objectStream, obj => ({ id: obj.name.toUpperCase(), doubledValue: obj.value * 2, })); const transformedObjects = await streamToArray(transformedObjectStream); const expectedObjects = objectsInput.map(obj => ({ id: obj.name.toUpperCase(), doubledValue: obj.value * 2, })); expect(transformedObjects).toEqual(expectedObjects); }); it("should handle empty stream", async () => { const emptyStream = createEmptyStream<number>(); const transformedEmptyStream = StreamUtil.transform( emptyStream, n => n * 2, ); const emptyResult = await streamToArray(transformedEmptyStream); expect(emptyResult).toEqual([]); }); it("should transform type (number -> string)", async () => { const numbersInput = [1, 2, 3, 4, 5]; const numberStream = createTestStream(numbersInput); const stringStream = StreamUtil.transform( numberStream, num => `Number: ${num}`, ); const stringResult = await streamToArray(stringStream); const expectedStrings = numbersInput.map(n => `Number: ${n}`); expect(stringResult).toEqual(expectedStrings); }); it("should handle error in transformer function", async () => { const stream = createNumberStream(1, 3); const transformedStream = StreamUtil.transform( stream, (num) => { if (num === 2) { throw new Error("Transform error"); } return num * 2; }, ); await expect(streamToArray(transformedStream)).rejects.toThrow("Transform error"); }); it("should handle null or undefined values in stream", async () => { const stream = createTestStream<number | null | undefined>([1, null, 3, undefined, 5]); const transformedStream = StreamUtil.transform( stream, num => (num ?? 0) * 2, ); const result = await streamToArray(transformedStream); expect(result).toEqual([2, 0, 6, 0, 10]); }); }); describe("toAsyncGenerator", () => { it("should yield a single value", async () => { const generator = toAsyncGenerator("test value"); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual(["test value"]); }); it("should handle null or undefined values", async () => { const generator = toAsyncGenerator(null); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual([null]); }); it("should handle object values", async () => { const testObj = { id: 1, name: "test" }; const generator = toAsyncGenerator(testObj); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual([testObj]); }); }); describe("streamDefaultReaderToAsyncGenerator", () => { it("should convert stream reader to async generator", async () => { const stream = createNumberStream(1, 5); const reader = stream.getReader(); const generator = streamDefaultReaderToAsyncGenerator(reader); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual([1, 2, 3, 4, 5]); }); it("should handle empty stream", async () => { const stream = createEmptyStream<number>(); const reader = stream.getReader(); const generator = streamDefaultReaderToAsyncGenerator(reader); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual([]); }); it("should handle null or undefined values in stream", async () => { const stream = createTestStream<number | null | undefined>([1, null, 3, undefined, 5]); const reader = stream.getReader(); const generator = streamDefaultReaderToAsyncGenerator(reader); const result = []; for await (const value of generator) { result.push(value); } expect(result).toEqual([1, null, 3, undefined, 5]); }); it("should handle error in stream", async () => { const stream = new ReadableStream<number>({ start(controller) { controller.enqueue(1); controller.enqueue(2); delay(1000).then(() => controller.error(new Error("Stream error"))).catch(() => {}); }, }); const reader = stream.getReader(); const generator = streamDefaultReaderToAsyncGenerator(reader); const result = []; try { for await (const value of generator) { result.push(value); } // Should not reach here expect(true).toBe(false); } catch (error) { console.error(error); expect(error).toBeInstanceOf(Error); expect((error as Error).message).toBe("Stream error"); expect(result).toEqual([1, 2]); } }); }); });