From d47ebf4d9d1172a16dba57f01f833df9c5699e84 Mon Sep 17 00:00:00 2001 From: Jeremy Moseley Date: Thu, 2 Jan 2025 12:16:29 -0800 Subject: [PATCH] feat: Improve the streaming API (#41) ## Proposed changes Improve the streaming API: * Assume the stream is always an `AsyncIteratorIterable` * The implementation only needs to return the stream * The component wrapper does the right thing based on the `props.stream` value. --- .vscode/settings.json | 8 +- examples/blogWriter/README.md | 2 +- examples/hackerNewsAnalyzer/README.md | 2 +- examples/hackerNewsAnalyzer/llm.ts | 49 +------- examples/streaming/README.md | 2 +- examples/streaming/chatCompletion.tsx | 7 +- examples/streaming/index.tsx | 47 ++++++-- examples/streaming/llm.ts | 49 +------- package.json | 2 +- packages/gensx-openai/tsconfig.json | 3 +- packages/gensx-openai/vitest.config.ts | 19 +++ packages/gensx/src/component.ts | 37 +++--- packages/gensx/src/jsx-runtime.ts | 60 ---------- packages/gensx/src/resolve.ts | 14 +-- packages/gensx/src/stream.ts | 34 ++---- packages/gensx/src/types.ts | 24 ++-- packages/gensx/tests/streaming.test.tsx | 150 ++++++++++++++++++++++++ packages/gensx/tsconfig.json | 4 +- packages/gensx/vitest.config.ts | 18 +++ pnpm-lock.yaml | 37 ------ 20 files changed, 295 insertions(+), 273 deletions(-) create mode 100644 packages/gensx/tests/streaming.test.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b77464b..889fef27 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,9 @@ "source.fixAll.eslint": "always", "source.removeUnusedImports": "always" }, - "cSpell.words": ["gensx", "jsxs"] -} + "cSpell.words": [ + "gensx", + "jsxs", + "Streamable" + ] +} \ No newline at end of file diff --git a/examples/blogWriter/README.md b/examples/blogWriter/README.md index 779e9070..d653f772 100644 --- a/examples/blogWriter/README.md +++ b/examples/blogWriter/README.md @@ -15,7 +15,7 @@ This example demonstrates how to use GenSX to create an AI-powered blog writing pnpm install # Run the example -pnpm start +pnpm run run ``` The example will generate a blog post based on the prompt "Write a blog post about the future of AI". You can modify the prompt in `index.tsx` to generate different content. diff --git a/examples/hackerNewsAnalyzer/README.md b/examples/hackerNewsAnalyzer/README.md index 10d29d74..e7a26073 100644 --- a/examples/hackerNewsAnalyzer/README.md +++ b/examples/hackerNewsAnalyzer/README.md @@ -16,7 +16,7 @@ This example demonstrates how to use GenSX to create a workflow that analyzes Ha pnpm install # Run the example -pnpm start +pnpm run run ``` The example will: diff --git a/examples/hackerNewsAnalyzer/llm.ts b/examples/hackerNewsAnalyzer/llm.ts index c1d6b48f..23d85be0 100644 --- a/examples/hackerNewsAnalyzer/llm.ts +++ b/examples/hackerNewsAnalyzer/llm.ts @@ -18,9 +18,8 @@ export interface LLMConfig { retryDelay?: number; } -export interface StreamResult { - value: Promise; - stream: () => AsyncIterator; +export interface StreamResult { + stream: () => AsyncIterableIterator; } class LLMError extends Error { @@ -43,9 +42,7 @@ export function createLLMService(config: LLMConfig) { } = config; // Chat with streaming support - async function chatStream( - messages: ChatMessage[], - ): Promise> { + async function chatStream(messages: ChatMessage[]): Promise { // Create a single streaming request const response = await openai.chat.completions.create({ model, @@ -55,45 +52,10 @@ export function createLLMService(config: LLMConfig) { stream: true, }); - // Split the stream into two - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const [stream1, stream2] = response.tee(); - - // Create a promise that will resolve with the full text - let fullText = ""; - const { - promise: valuePromise, - resolve: resolveValue, - reject: rejectValue, - } = (() => { - let resolve: (value: string) => void; - let reject: (error: Error) => void; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve: resolve!, reject: reject! }; - })(); - - // Accumulate the full text in the background using stream1 - (async () => { - try { - for await (const chunk of stream1) { - const content = chunk.choices[0]?.delta?.content ?? ""; - if (content) { - fullText += content; - } - } - resolveValue(fullText); - } catch (e) { - rejectValue(e instanceof Error ? e : new Error(String(e))); - } - })().catch(rejectValue); // Handle floating promise - // Create a stream generator function that yields chunks immediately from stream2 const getStream = async function* () { try { - for await (const chunk of stream2) { + for await (const chunk of response) { const content = chunk.choices[0]?.delta?.content ?? ""; if (content) { yield content; @@ -106,7 +68,6 @@ export function createLLMService(config: LLMConfig) { return { stream: getStream, - value: valuePromise, }; } @@ -156,7 +117,7 @@ export function createLLMService(config: LLMConfig) { } // Complete with streaming support - async function completeStream(prompt: string): Promise> { + async function completeStream(prompt: string): Promise { return chatStream([{ role: "user", content: prompt }]); } diff --git a/examples/streaming/README.md b/examples/streaming/README.md index 1442018a..2b4303fd 100644 --- a/examples/streaming/README.md +++ b/examples/streaming/README.md @@ -16,7 +16,7 @@ This example demonstrates how to use GenSX's streaming capabilities with LLM res pnpm install # Run the example -pnpm start +pnpm run run ``` The example will run two versions of the same prompt: diff --git a/examples/streaming/chatCompletion.tsx b/examples/streaming/chatCompletion.tsx index 72259961..2a2520d9 100644 --- a/examples/streaming/chatCompletion.tsx +++ b/examples/streaming/chatCompletion.tsx @@ -11,14 +11,11 @@ interface ChatCompletionProps { prompt: string; } -export const ChatCompletion = gsx.StreamComponent( +export const ChatCompletion = gsx.StreamComponent( async ({ prompt }) => { // Use the LLM service's streaming API const result = await llm.completeStream(prompt); - return { - stream: () => result.stream(), - value: result.value, - }; + return result.stream(); }, ); diff --git a/examples/streaming/index.tsx b/examples/streaming/index.tsx index fcf58f1c..91ac5535 100644 --- a/examples/streaming/index.tsx +++ b/examples/streaming/index.tsx @@ -1,8 +1,8 @@ import { gsx, Streamable } from "gensx"; +import { setTimeout } from "timers/promises"; import { ChatCompletion } from "./chatCompletion.js"; -// Example 3: Streaming vs non-streaming chat completion async function runStreamingWithChildrenExample() { const prompt = "Write a 250 word story about an AI that discovers the meaning of friendship through a series of small interactions with humans. Be concise but meaningful."; @@ -21,11 +21,9 @@ async function runStreamingWithChildrenExample() { console.log("\nšŸ“ Streaming version (processing tokens as they arrive):"); await gsx.execute( - {async (response: Streamable) => { + {async (response: Streamable) => { // Print tokens as they arrive - for await (const token of { - [Symbol.asyncIterator]: () => response.stream(), - }) { + for await (const token of response) { process.stdout.write(token); } process.stdout.write("\n"); @@ -48,23 +46,54 @@ async function runStreamingExample() { console.log("āœ… Complete response:", finalResult); console.log("\nšŸ“ Streaming version (processing tokens as they arrive):"); - const response = await gsx.execute>( + const response = await gsx.execute( , ); - for await (const token of { - [Symbol.asyncIterator]: () => response.stream(), - }) { + for await (const token of response) { process.stdout.write(token); } process.stdout.write("\n"); console.log("āœ… Streaming complete"); } +const GeneratorComponent = gsx.StreamComponent<{ + foo: string; + iterations: number; +}>(async function* ({ foo, iterations }) { + await setTimeout(10); + for (let i = 1; i < iterations + 1; i++) { + console.log("šŸ”„ GeneratorComponent", i); + yield `${i}: ${foo.repeat(i)}\n`; + await setTimeout(10); + } +}); + +async function streamingGeneratorExample() { + console.log("āš”ļø StreamingGeneratorExample - return result from generator"); + const response1 = await gsx.execute( + , + ); + console.log(`āœ… Streaming complete:\n====\n${response1}====`); + console.log("āš”ļø StreamingGeneratorExample - process generator result"); + await gsx.execute( + + {async (response: Streamable) => { + for await (const token of response) { + process.stdout.write(token); + } + process.stdout.write("\n"); + console.log("āœ… Streaming complete"); + }} + , + ); +} + // Main function to run examples async function main() { await runStreamingWithChildrenExample(); await runStreamingExample(); + await streamingGeneratorExample(); } main().catch(console.error); diff --git a/examples/streaming/llm.ts b/examples/streaming/llm.ts index c1d6b48f..23d85be0 100644 --- a/examples/streaming/llm.ts +++ b/examples/streaming/llm.ts @@ -18,9 +18,8 @@ export interface LLMConfig { retryDelay?: number; } -export interface StreamResult { - value: Promise; - stream: () => AsyncIterator; +export interface StreamResult { + stream: () => AsyncIterableIterator; } class LLMError extends Error { @@ -43,9 +42,7 @@ export function createLLMService(config: LLMConfig) { } = config; // Chat with streaming support - async function chatStream( - messages: ChatMessage[], - ): Promise> { + async function chatStream(messages: ChatMessage[]): Promise { // Create a single streaming request const response = await openai.chat.completions.create({ model, @@ -55,45 +52,10 @@ export function createLLMService(config: LLMConfig) { stream: true, }); - // Split the stream into two - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const [stream1, stream2] = response.tee(); - - // Create a promise that will resolve with the full text - let fullText = ""; - const { - promise: valuePromise, - resolve: resolveValue, - reject: rejectValue, - } = (() => { - let resolve: (value: string) => void; - let reject: (error: Error) => void; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve: resolve!, reject: reject! }; - })(); - - // Accumulate the full text in the background using stream1 - (async () => { - try { - for await (const chunk of stream1) { - const content = chunk.choices[0]?.delta?.content ?? ""; - if (content) { - fullText += content; - } - } - resolveValue(fullText); - } catch (e) { - rejectValue(e instanceof Error ? e : new Error(String(e))); - } - })().catch(rejectValue); // Handle floating promise - // Create a stream generator function that yields chunks immediately from stream2 const getStream = async function* () { try { - for await (const chunk of stream2) { + for await (const chunk of response) { const content = chunk.choices[0]?.delta?.content ?? ""; if (content) { yield content; @@ -106,7 +68,6 @@ export function createLLMService(config: LLMConfig) { return { stream: getStream, - value: valuePromise, }; } @@ -156,7 +117,7 @@ export function createLLMService(config: LLMConfig) { } // Complete with streaming support - async function completeStream(prompt: string): Promise> { + async function completeStream(prompt: string): Promise { return chatStream([{ role: "user", content: prompt }]); } diff --git a/package.json b/package.json index e199c95e..9d1819b8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "build": "turbo run build --filter=\"./packages/*\"", "build:examples": "turbo run build --filter=\"./examples/*\"", "build:all": "turbo run build", - "dev": "turbo run dev", + "dev": "turbo run dev --filter=\"./packages/*\"", "lint": "turbo run lint --filter=\"./packages/*\"", "lint:examples": "turbo run lint --filter=\"./examples/*\"", "lint:all": "turbo run lint", diff --git a/packages/gensx-openai/tsconfig.json b/packages/gensx-openai/tsconfig.json index f69434da..e61b5a04 100644 --- a/packages/gensx-openai/tsconfig.json +++ b/packages/gensx-openai/tsconfig.json @@ -5,6 +5,7 @@ "lib": [ "ES2020" ], + "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, @@ -21,7 +22,7 @@ "gensx": [ "../gensx/src" ] - }, + } }, "exclude": [ "node_modules", diff --git a/packages/gensx-openai/vitest.config.ts b/packages/gensx-openai/vitest.config.ts index 6921512a..5651de6f 100644 --- a/packages/gensx-openai/vitest.config.ts +++ b/packages/gensx-openai/vitest.config.ts @@ -1,7 +1,26 @@ +// import swc from "unplugin-swc"; +import * as path from "path"; + import { loadEnv } from "vite"; import { defineConfig } from "vitest/config"; +import tsconfig from "./tsconfig.json"; + +// Create an alias object from the paths in tsconfig.json +const alias = Object.fromEntries( + // For Each Path in tsconfig.json + Object.entries(tsconfig.compilerOptions.paths).map(([key, [value]]) => [ + // Remove the "/*" from the key and resolve the path + key.replace("/*", ""), + // Remove the "/*" from the value Resolve the relative path + path.resolve(__dirname, value.replace("/*", "")), + ]), +); + export default defineConfig({ + resolve: { + alias, + }, test: { root: "./", globals: true, diff --git a/packages/gensx/src/component.ts b/packages/gensx/src/component.ts index af8e7933..47617298 100644 --- a/packages/gensx/src/component.ts +++ b/packages/gensx/src/component.ts @@ -2,8 +2,8 @@ import type { ComponentProps, MaybePromise, Streamable, - StreamComponent, StreamComponentProps, + StreamingComponent, WorkflowComponent, } from "./types"; @@ -23,21 +23,29 @@ export function Component( }); } - // Mark as workflow component and JSX element type const component = GsxComponent as WorkflowComponent; - return component; } -export function StreamComponent( - fn: (props: P) => MaybePromise>, -): StreamComponent { - function GsxStreamComponent( - props: StreamComponentProps, - ): MaybePromise> { - return withContext({ streaming: props.stream ?? false }, async () => - Promise.resolve(fn(props)), - ); +export function StreamComponent

( + fn: ( + props: P, + ) => MaybePromise | Generator>, +): StreamingComponent { + function GsxStreamComponent( + props: StreamComponentProps, + ): MaybePromise { + return withContext({ streaming: props.stream ?? false }, async () => { + const iterator = await Promise.resolve(fn(props)); + if (props.stream) { + return iterator as Stream extends true ? Streamable : string; + } + let result = ""; + for await (const token of iterator) { + result += token; + } + return result as Stream extends true ? Streamable : string; + }); } if (fn.name) { @@ -46,9 +54,6 @@ export function StreamComponent( }); } - // Mark as stream component - const component = GsxStreamComponent as StreamComponent; - component.isStreamComponent = true; - + const component = GsxStreamComponent as StreamingComponent; return component; } diff --git a/packages/gensx/src/jsx-runtime.ts b/packages/gensx/src/jsx-runtime.ts index d4a09f48..52a7d5b7 100644 --- a/packages/gensx/src/jsx-runtime.ts +++ b/packages/gensx/src/jsx-runtime.ts @@ -4,8 +4,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import type { Streamable } from "./types"; - import { resolveDeep } from "./resolve"; export namespace JSX { @@ -27,18 +25,6 @@ export const Fragment = (props: { return [props.children]; }; -// Helper to check if something is a streamable result -function isStreamable(value: unknown): value is Streamable { - return ( - typeof value === "object" && - value !== null && - "stream" in value && - "value" in value && - typeof (value as Streamable).stream === "function" && - value.value instanceof Promise - ); -} - export const jsx = < TOutput, TProps extends Record & { @@ -64,55 +50,9 @@ export const jsx = < // Execute component const rawResult = await component(props ?? ({} as TProps)); - // If this is a streaming result, handle it specially - if (isStreamable(rawResult)) { - const hasChildFunction = typeof children === "function"; - const isStreamingComponent = - "isStreamComponent" in component && - component.isStreamComponent === true; - const shouldStream = isStreamingComponent && (props?.stream ?? false); - - if (!hasChildFunction) { - // When no function children, preserve the streamable if explicitly streaming - if (shouldStream) { - return rawResult as Awaited; - } - // Not streaming, resolve the value - return await rawResult.value; - } - - if (shouldStream) { - // Pass the streamable to children function - const childrenResult = await (children as Function)(rawResult); - const resolvedResult = await resolveDeep(childrenResult); - return resolvedResult as Awaited; - } else { - // Not streaming, resolve the value first - const resolvedValue = await rawResult.value; - const childrenResult = await (children as Function)( - resolvedValue as TOutput, - ); - const resolvedResult = await resolveDeep(childrenResult); - return resolvedResult as Awaited; - } - } - // For non-streaming results, resolve deeply but preserve streamables const result = await resolveDeep(rawResult); - // Check again after deep resolution in case we got a streamable - if (isStreamable(result)) { - const isStreamingComponent = - "isStreamComponent" in component && - component.isStreamComponent === true; - const shouldStream = isStreamingComponent && (props?.stream ?? false); - if (shouldStream) { - return result as Awaited; - } - // Not streaming, resolve the value - return await result.value; - } - // If there are no function children, return the resolved result if (typeof children !== "function") { return result as Awaited; diff --git a/packages/gensx/src/resolve.ts b/packages/gensx/src/resolve.ts index 092f9f81..3fb25242 100644 --- a/packages/gensx/src/resolve.ts +++ b/packages/gensx/src/resolve.ts @@ -2,19 +2,9 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/return-await */ -import type { ExecutableValue, Streamable } from "./types"; +import type { ExecutableValue } from "./types"; -// Helper to check if something is a streamable value -function isStreamable(value: unknown): value is Streamable { - return ( - typeof value === "object" && - value !== null && - "stream" in value && - "value" in value && - typeof (value as { stream: unknown }).stream === "function" && - (value as { value: unknown }).value instanceof Promise - ); -} +import { isStreamable } from "./stream"; /** * Deeply resolves any value, handling promises, arrays, objects, and JSX elements. diff --git a/packages/gensx/src/stream.ts b/packages/gensx/src/stream.ts index 2cb8256a..78119fd6 100644 --- a/packages/gensx/src/stream.ts +++ b/packages/gensx/src/stream.ts @@ -1,33 +1,15 @@ -import type { Streamable, StreamComponent } from "./types"; - -import { getCurrentContext } from "./context"; - -// Helper to check if a component is a stream component -export function isStreamComponent( - component: unknown, -): component is StreamComponent { - return ( - typeof component === "function" && - "isStreamComponent" in component && - component.isStreamComponent === true - ); -} +import type { Streamable } from "./types"; // Helper to check if something is a streamable value -export function isStreamable(value: unknown): value is Streamable { +export function isStreamable(value: unknown): value is Streamable { return ( typeof value === "object" && value !== null && - "stream" in value && - "value" in value && - typeof (value as Streamable).stream === "function" && - value.value instanceof Promise + // Verify that it's an async iterator + "next" in value && + typeof (value as AsyncIterator).next === "function" && + // Verify that it has the async iterator symbol + Symbol.asyncIterator in value && + typeof value[Symbol.asyncIterator] === "function" ); } - -// Helper to check if we're in a streaming context -export function isInStreamingContext(): boolean { - const context = getCurrentContext(); - const result = context.hasStreamingInChain(); - return result; -} diff --git a/packages/gensx/src/types.ts b/packages/gensx/src/types.ts index 2f7db781..fdaa0545 100644 --- a/packages/gensx/src/types.ts +++ b/packages/gensx/src/types.ts @@ -31,18 +31,22 @@ export type ComponentProps = BaseProps

& { children?: (output: O) => MaybePromise; }; -export interface Streamable { - value: Promise; - stream: () => AsyncIterator; -} +export type Streamable = AsyncIterableIterator; // Stream component props as a type alias -export type StreamComponentProps = BaseProps

& { - stream?: boolean; - children?: (output: Streamable) => MaybePromise; +export type StreamComponentProps< + P, + Stream extends boolean | undefined, +> = BaseProps

& { + stream?: Stream; + children?: ( + output: Stream extends true ? Streamable : string, + ) => MaybePromise; }; -export interface StreamComponent extends JSX.ElementType { - (props: StreamComponentProps): MaybePromise>; - isStreamComponent?: true; +export interface StreamingComponent + extends JSX.ElementType { + ( + props: StreamComponentProps, + ): MaybePromise; } diff --git a/packages/gensx/tests/streaming.test.tsx b/packages/gensx/tests/streaming.test.tsx new file mode 100644 index 00000000..ed07ef01 --- /dev/null +++ b/packages/gensx/tests/streaming.test.tsx @@ -0,0 +1,150 @@ +import { setTimeout } from "timers/promises"; + +import { gsx, Streamable } from "@/index"; + +suite("streaming", () => { + suite("for an AsyncIterableIterator", () => { + const Component = gsx.StreamComponent<{ foo: string }>(function ({ foo }) { + // const result = await llm.completeStream(prompt); + // return result.stream(); + function* stream() { + yield "Hello "; + yield "World"; + yield "!\n\n"; + yield "H"; + yield "e"; + yield "r"; + yield "e"; + yield " "; + yield "i"; + yield "s"; + yield " "; + yield "t"; + yield "h"; + yield "e"; + yield " "; + yield "p"; + yield "r"; + yield "o"; + yield "m"; + yield "p"; + yield "t"; + yield "\n"; + for (const char of foo) { + yield char; + } + } + const generator = stream(); + const iterator: AsyncIterableIterator = { + next: async () => { + const result = generator.next(); + await setTimeout(10); + return result; + }, + [Symbol.asyncIterator]: () => iterator, + }; + + return iterator; + }); + + test("returns the results directly", async () => { + const result = await gsx.execute(); + expect(result).toEqual("Hello World!\n\nHere is the prompt\nbar"); + }); + + test("returns a streamable", async () => { + const result = await gsx.execute( + , + ); + expect("next" in result).toBe(true); + expect(Symbol.asyncIterator in result).toBe(true); + let accumulated = ""; + for await (const token of result) { + accumulated += token; + } + expect(accumulated).toEqual("Hello World!\n\nHere is the prompt\nbar"); + }); + + test("can be used with children as a stream", async () => { + let result = ""; + await gsx.execute( + + {async (response: Streamable) => { + for await (const token of response) { + result += token; + } + }} + , + ); + expect(result).toEqual("Hello World!\n\nHere is the prompt\nbar"); + }); + + test("can be used with children without streaming", async () => { + let result = ""; + await gsx.execute( + + {(response: string) => (result = response)} + , + ); + expect(result).toEqual("Hello World!\n\nHere is the prompt\nbar"); + }); + }); + + suite("for a Generator function", () => { + const Component = gsx.StreamComponent<{ foo: string }>(async function* ({ + foo, + }) { + yield "Hello "; + yield "World"; + yield "!\n\n"; + for (const char of foo) { + await setTimeout(1); + yield char; + } + }); + + test("returns the results directly", async () => { + const result = await gsx.execute(); + expect(result).toEqual("Hello World!\n\nbar"); + }); + + test("returns a streamable", async () => { + const result = await gsx.execute( + , + ); + expect("next" in result).toBe(true); + expect(Symbol.asyncIterator in result).toBe(true); + let accumulated = ""; + for await (const token of result) { + accumulated += token; + } + expect(accumulated).toEqual("Hello World!\n\nbar"); + }); + + test("can be used with children as a stream", async () => { + let result = ""; + await gsx.execute( + + {async (response: Streamable) => { + for await (const token of response) { + result += token; + } + }} + , + ); + expect(result).toEqual("Hello World!\n\nbar"); + }); + + test("can be used with children without streaming", async () => { + let result = ""; + await gsx.execute( + + {(response: string) => { + result = response; + }} + , + ); + expect(result).toEqual("Hello World!\n\nbar"); + }); + }); +}); diff --git a/packages/gensx/tsconfig.json b/packages/gensx/tsconfig.json index 062d2069..b370e5e3 100644 --- a/packages/gensx/tsconfig.json +++ b/packages/gensx/tsconfig.json @@ -5,10 +5,9 @@ "useDefineForClassFields": true, "module": "esnext", "lib": [ - "ESNext", + "ESNext" ], "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, @@ -19,7 +18,6 @@ "sourceMap": true, "inlineSources": true, "esModuleInterop": true, - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, diff --git a/packages/gensx/vitest.config.ts b/packages/gensx/vitest.config.ts index ee0ba9ed..5651de6f 100644 --- a/packages/gensx/vitest.config.ts +++ b/packages/gensx/vitest.config.ts @@ -1,8 +1,26 @@ // import swc from "unplugin-swc"; +import * as path from "path"; + import { loadEnv } from "vite"; import { defineConfig } from "vitest/config"; +import tsconfig from "./tsconfig.json"; + +// Create an alias object from the paths in tsconfig.json +const alias = Object.fromEntries( + // For Each Path in tsconfig.json + Object.entries(tsconfig.compilerOptions.paths).map(([key, [value]]) => [ + // Remove the "/*" from the key and resolve the path + key.replace("/*", ""), + // Remove the "/*" from the value Resolve the relative path + path.resolve(__dirname, value.replace("/*", "")), + ]), +); + export default defineConfig({ + resolve: { + alias, + }, test: { root: "./", globals: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8cf91eb..2a8fdb9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,43 +119,6 @@ importers: specifier: ^5.0.0 version: 5.7.2 - examples/playground: - dependencies: - gensx: - specifier: workspace:* - version: link:../../packages/gensx - openai: - specifier: ^4.77.0 - version: 4.77.0 - devDependencies: - '@types/node': - specifier: ^20.0.0 - version: 20.17.10 - '@typescript-eslint/eslint-plugin': - specifier: ^6.0.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/parser': - specifier: ^6.0.0 - version: 6.21.0(eslint@8.57.1)(typescript@5.7.2) - eslint: - specifier: ^8.0.0 - version: 8.57.1 - nodemon: - specifier: ^3.1.9 - version: 3.1.9 - prettier: - specifier: ^3.0.0 - version: 3.4.2 - ts-node-dev: - specifier: ^2.0.0 - version: 2.0.0(@swc/core@1.10.1)(@types/node@20.17.10)(typescript@5.7.2) - tsx: - specifier: ^4.19.2 - version: 4.19.2 - typescript: - specifier: ^5.0.0 - version: 5.7.2 - examples/streaming: dependencies: gensx: