Compare commits

...

16 Commits

Author SHA1 Message Date
Pierre b41f36c8eb Change any to Usage for class ChatMessage 2023-12-21 19:17:19 +01:00
Pierre 54a910fc86 Add some doc on usage 2023-12-09 18:17:26 +01:00
Pierre ca76e0c221 Expend support for replicate / [usage/metrics] field in chatCompletion object for tracking of current query consumption 2023-12-05 17:38:40 +01:00
Pierre 90d259c7b0 Initial: Add a way to track lifetime usage and cost with OpenAI 2023-12-05 16:20:57 +01:00
yisding 54ca85d482 Merge pull request #229 from run-llama/feat/add-assemblyai
Feat: Re-Add AssemblyAIReader
2023-12-04 22:02:45 -08:00
yisding 65ef0be90c Merge pull request #255 from run-llama/ms/fix-pgvector
fix: paths in pgvector store example
2023-12-04 22:00:23 -08:00
yisding 57106affdf Merge pull request #256 from run-llama/ms/move-mongodb-example
Chore: move mongodb example
2023-12-04 21:59:55 -08:00
yisding d613bbd358 Merge pull request #257 from run-llama/ms/download-community-projects
Feat: Add support for installing templates from community repo
2023-12-04 09:08:01 -08:00
Marcus Schiesser 36f0af5a5d refactor: factor out questions and use strong-typing for question args 2023-12-04 17:07:49 +07:00
Marcus Schiesser 79d7076121 fix: only use community path for community templates 2023-12-04 17:02:53 +07:00
Marcus Schiesser 526b3e74bf fix: using correct path for readme link in terminal 2023-12-04 17:01:44 +07:00
thucpn d03dc21e8a feat: options to download community projects 2023-12-04 13:51:53 +07:00
Marcus Schiesser c31dfa4957 fix: move mongodb to examples/ 2023-12-04 11:40:03 +07:00
Niels Swimberghe 1fe02a3067 update AssemblyAI reader to use new functions (#245) 2023-11-29 10:05:36 +08:00
Marcus Schiesser 31cf3cde45 fix: default order error in package.json 2023-11-28 15:50:10 +07:00
Marcus Schiesser 11f0c2cab1 Revert "feat: remove AssemblyAIReader as it's not working with Next.JS"
This reverts commit c8bbc101cc.
2023-11-28 15:48:08 +07:00
30 changed files with 797 additions and 408 deletions
+40
View File
@@ -20,3 +20,43 @@ const serviceContext = serviceContextFromDefaults({ llm: openaiLLM });
- [OpenAI](../../api/classes/OpenAI.md)
- [ServiceContext](../../api/interfaces/ServiceContext.md)
## Usage
The LLM object tracks API consumption across all your code. This is done through the `llm.usage` property.
_Note: Usage is not supported for stream calls_
```javascript
import { OpenAI } from "llamaindex";
// Create a new instance of OpenAI with the specified model and temperature
const llm = new OpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
async function ask() {
// Send a chat request to the OpenAI API
const response = await llm.chat([
{
role: "system",
content: "You are a helpful llama.",
},
{
role: "user",
content: "Where do llama live?",
},
]);
// Log the response from the API
console.log(response);
/**
The response includes a message from the assistant and usage information.
The usage information includes the number of prompt tokens, completion tokens, and total tokens used.
*/
// Log the usage information from the LLM object
console.log(llm.usage);
/**
The usage object includes the number of prompt tokens, completion tokens, the cost, and the compute seconds.
*/
}
```
-20
View File
@@ -1,20 +0,0 @@
# mongodb-llamaindexts
## 0.0.3
### Patch Changes
- Updated dependencies [3bab231]
- llamaindex@0.0.37
## 0.0.2
### Patch Changes
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- Updated dependencies
- llamaindex@0.0.36
-17
View File
@@ -1,17 +0,0 @@
{
"version": "0.0.3",
"private": true,
"name": "mongodb-llamaindexts",
"dependencies": {
"llamaindex": "workspace:*",
"dotenv": "^16.3.1",
"mongodb": "^6.2.0"
},
"devDependencies": {
"@types/node": "^18.18.6",
"ts-node": "^10.9.1"
},
"scripts": {
"lint": "eslint ."
}
}
+59
View File
@@ -0,0 +1,59 @@
import { program } from "commander";
import { AudioTranscriptReader, TranscribeParams } from "llamaindex";
import { stdin as input, stdout as output } from "node:process";
// readline/promises is still experimental so not in @types/node yet
// @ts-ignore
import readline from "node:readline/promises";
import { VectorStoreIndex } from "../../packages/core/src/indices";
program
.option(
"-a, --audio-url [string]",
"URL or path of the audio file to transcribe",
)
.option("-i, --transcript-id [string]", "ID of the AssemblyAI transcript")
.action(async (options) => {
if (!process.env.ASSEMBLYAI_API_KEY) {
console.log("No ASSEMBLYAI_API_KEY found in environment variables.");
return;
}
const reader = new AudioTranscriptReader();
let params: TranscribeParams | string;
if (options.audioUrl) {
params = {
audio: options.audioUrl,
};
} else if (options.transcriptId) {
params = options.transcriptId;
} else {
console.log(
"You must provide either an --audio-url or a --transcript-id",
);
return;
}
const documents = await reader.loadData(params);
console.log(documents);
// Split text and create embeddings. Store them in a VectorStoreIndex
const index = await VectorStoreIndex.fromDocuments(documents);
// Create query engine
const queryEngine = index.asQueryEngine();
const rl = readline.createInterface({ input, output });
while (true) {
const query = await rl.question("Ask a question: ");
if (!query) {
break;
}
const response = await queryEngine.query(query);
console.log(response.toString());
}
});
program.parse();
+59
View File
@@ -0,0 +1,59 @@
import { program } from "commander";
import { AudioTranscriptReader, TranscribeParams } from "llamaindex";
import { stdin as input, stdout as output } from "node:process";
// readline/promises is still experimental so not in @types/node yet
// @ts-ignore
import readline from "node:readline/promises";
import { VectorStoreIndex } from "../../packages/core/src/indices";
program
.option(
"-a, --audio-url [string]",
"URL or path of the audio file to transcribe",
)
.option("-i, --transcript-id [string]", "ID of the AssemblyAI transcript")
.action(async (options) => {
if (!process.env.ASSEMBLYAI_API_KEY) {
console.log("No ASSEMBLYAI_API_KEY found in environment variables.");
return;
}
const reader = new AudioTranscriptReader();
let params: TranscribeParams | string;
if (options.audioUrl) {
params = {
audio: options.audioUrl,
};
} else if (options.transcriptId) {
params = options.transcriptId;
} else {
console.log(
"You must provide either an --audio-url or a --transcript-id",
);
return;
}
const documents = await reader.loadData(params);
console.log(documents);
// Split text and create embeddings. Store them in a VectorStoreIndex
const index = await VectorStoreIndex.fromDocuments(documents);
// Create query engine
const queryEngine = index.asQueryEngine();
const rl = readline.createInterface({ input, output });
while (true) {
const query = await rl.question("Ask a question: ");
if (!query) {
break;
}
const response = await queryEngine.query(query);
console.log(response.toString());
}
});
program.parse();
@@ -6,7 +6,7 @@ import { MongoClient } from "mongodb";
// Load environment variables from local .env file
dotenv.config();
const jsonFile = "tinytweets.json";
const jsonFile = "./data/tinytweets.json";
const mongoUri = process.env.MONGODB_URI!;
const databaseName = process.env.MONGODB_DATABASE!;
const collectionName = process.env.MONGODB_COLLECTION!;
@@ -2,12 +2,7 @@
### Prepare Environment
Make sure to run `pnpm install` and set your OpenAI environment variable before running these examples.
```
pnpm install
export OPENAI_API_KEY="sk-..."
```
Read and follow the instructions in the [README.md](../README.md) file located one directory up to make sure your JS/TS dependencies are set up. The commands listed below are also run from that parent directory.
### Sign up for MongoDB Atlas
@@ -21,7 +16,7 @@ The signup process will walk you through the process of creating your cluster an
### Set up environment variables
Copy the connection string (make sure you include your password) and put it into a file called `.env` in the root of this repo. It should look like this:
Copy the connection string (make sure you include your password) and put it into a file called `.env` in the parent folder of this directory. It should look like this:
```
MONGODB_URI=mongodb+srv://seldo:xxxxxxxxxxx@llamaindexdemocluster.xfrdhpz.mongodb.net/?retryWrites=true&w=majority
@@ -39,7 +34,7 @@ MONGODB_COLLECTION=tiny_tweets_collection
You are now ready to import our ready-made data set into Mongo. This is the file `tinytweets.json`, a selection of approximately 1000 tweets from @seldo on Twitter in mid-2019. With your environment set up you can do this by running
```
pnpm ts-node 1_import.ts
npx ts-node mongodb/1_import.ts
```
If you don't want to use tweets, you can replace `json_file` with any other array of JSON objects, but you will need to modify some code later to make sure the correct field gets indexed. There is no LlamaIndex-specific code here; you can load your data into Mongo any way you want to.
@@ -64,7 +59,7 @@ MONGODB_VECTOR_INDEX=tiny_tweets_vector_index
If the data you're indexing is the tweets we gave you, you're ready to go:
```bash
pnpm ts-node 2_load_and_index.ts
npx ts-node mongodb/2_load_and_index.ts
```
> Note: this script is running a couple of minutes and currently doesn't show any progress.
@@ -119,7 +114,7 @@ Now you're ready to query your data!
You can do this by running
```bash
pnpm ts-node 3_query.ts
npx ts-node mongodb/3_query.ts
```
This sets up a connection to Atlas just like `2_load_and_index.ts` did, then it creates a [query engine](https://docs.llamaindex.ai/en/stable/understanding/querying/querying.html#getting-started) and runs a query against it.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 360 KiB

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

+4 -2
View File
@@ -6,7 +6,9 @@
"@notionhq/client": "^2.2.13",
"@pinecone-database/pinecone": "^1.1.2",
"commander": "^11.1.0",
"llamaindex": "latest"
"llamaindex": "latest",
"dotenv": "^16.3.1",
"mongodb": "^6.2.0"
},
"devDependencies": {
"@types/node": "^18.18.6",
@@ -15,4 +17,4 @@
"scripts": {
"lint": "eslint ."
}
}
}
+1
View File
@@ -6,6 +6,7 @@
"@anthropic-ai/sdk": "^0.9.1",
"@notionhq/client": "^2.2.13",
"@xenova/transformers": "^2.8.0",
"assemblyai": "^3.1.3",
"crypto-js": "^4.2.0",
"js-tiktoken": "^1.0.8",
"lodash": "^4.17.21",
+1
View File
@@ -19,6 +19,7 @@ export * from "./constants";
export * from "./embeddings";
export * from "./indices";
export * from "./llm/LLM";
export * from "./readers/AssemblyAI";
export * from "./readers/CSVReader";
export * from "./readers/HTMLReader";
export * from "./readers/MarkdownReader";
+91 -12
View File
@@ -45,6 +45,8 @@ export interface ChatResponse {
message: ChatMessage;
raw?: Record<string, any>;
delta?: string;
metrics?: any;
usage?: Usage;
}
// NOTE in case we need CompletionResponse to diverge from ChatResponse in the future
@@ -98,19 +100,44 @@ export interface LLM {
* Calculates the number of tokens needed for the given chat messages
*/
tokens(messages: ChatMessage[]): number;
/**
* Returns the usage information of the LLM
*/
usage: Usage;
}
export const GPT4_MODELS = {
"gpt-4": { contextWindow: 8192 },
"gpt-4-32k": { contextWindow: 32768 },
"gpt-4-1106-preview": { contextWindow: 128000 },
"gpt-4-vision-preview": { contextWindow: 8192 },
"gpt-4": { contextWindow: 8192, promptCost: 0.03, completionCost: 0.06 },
"gpt-4-32k": { contextWindow: 32768, promptCost: 0.06, completionCost: 0.12 },
"gpt-4-1106-preview": {
contextWindow: 128000,
promptCost: 0.01,
completionCost: 0.03,
},
"gpt-4-vision-preview": {
contextWindow: 8192,
promptCost: 0.01,
completionCost: 0.03,
},
};
export const GPT35_MODELS = {
"gpt-3.5-turbo": { contextWindow: 4096 },
"gpt-3.5-turbo-16k": { contextWindow: 16384 },
"gpt-3.5-turbo-1106": { contextWindow: 16384 },
"gpt-3.5-turbo": {
contextWindow: 4096,
promptCost: 0.001,
completionCost: 0.002,
},
"gpt-3.5-turbo-16k": {
contextWindow: 16384,
promptCost: 0.001,
completionCost: 0.002,
},
"gpt-3.5-turbo-1106": {
contextWindow: 16384,
promptCost: 0.001,
completionCost: 0.002,
},
};
/**
@@ -121,6 +148,19 @@ export const ALL_AVAILABLE_OPENAI_MODELS = {
...GPT35_MODELS,
};
export class Usage {
promptTokens: number;
completionTokens: number;
computeSeconds: number;
cost: number;
constructor() {
this.promptTokens = 0;
this.completionTokens = 0;
this.cost = 0;
this.computeSeconds = 0;
}
}
/**
* OpenAI LLM implementation
*/
@@ -149,6 +189,7 @@ export class OpenAI implements LLM {
callbackManager?: CallbackManager;
usage: Usage;
constructor(
init?: Partial<OpenAI> & {
azure?: AzureOpenAIConfig;
@@ -164,6 +205,8 @@ export class OpenAI implements LLM {
this.additionalChatOptions = init?.additionalChatOptions;
this.additionalSessionOptions = init?.additionalSessionOptions;
this.usage = new Usage();
if (init?.azure || shouldUseAzure()) {
const azureConfig = getAzureConfigFromEnv({
...init?.azure,
@@ -278,8 +321,21 @@ export class OpenAI implements LLM {
});
const content = response.choices[0].message?.content ?? "";
// Update usage
this.usage.promptTokens += response.usage?.prompt_tokens || 0;
this.usage.completionTokens += response.usage?.completion_tokens || 0;
this.usage.cost +=
((response.usage?.prompt_tokens || 0) *
ALL_AVAILABLE_OPENAI_MODELS[this.model].promptCost) /
1000 +
((response.usage?.completion_tokens || 0) *
ALL_AVAILABLE_OPENAI_MODELS[this.model].completionCost) /
1000;
return {
message: { content, role: response.choices[0].message.role },
usage: response.usage,
} as R;
}
@@ -373,23 +429,27 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
replicateApi:
"replicate/llama70b-v2-chat:e951f18578850b652510200860fc4ea62b3b16fac280f83ff32282f87bbd2e48",
//^ Previous 70b model. This is also actually 4 bit, although not exllama.
costPerSecond: 0.0014,
},
"Llama-2-70b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
//^ Model is based off of exllama 4bit.
costPerSecond: 0.0014,
},
"Llama-2-13b-chat-old": {
contextWindow: 4096,
replicateApi:
"a16z-infra/llama13b-v2-chat:df7690f1994d94e96ad9d568eac121aecf50684a0b0963b25a41cc40061269e5",
costPerSecond: 0.000725,
},
//^ Last known good 13b non-quantized model. In future versions they add the SYS and INST tags themselves
"Llama-2-13b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-13b-chat:f4e2de70d66816a838a89eeeb621910adffb0dd0baba3976c96980970978018d",
costPerSecond: 0.000725,
},
"Llama-2-7b-chat-old": {
contextWindow: 4096,
@@ -399,11 +459,13 @@ export const ALL_AVAILABLE_LLAMADEUCE_MODELS = {
// tags themselves
// https://github.com/replicate/cog-llama-template/commit/fa5ce83912cf82fc2b9c01a4e9dc9bff6f2ef137
// Problem is that they fix the max_new_tokens issue in the same commit. :-(
costPerSecond: 0.000725,
},
"Llama-2-7b-chat-4bit": {
contextWindow: 4096,
replicateApi:
"meta/llama-2-7b-chat:13c3cdee13ee059ab779f0291d29054dab00a47dad8261375654de5540165fb0",
costPerSecond: 0.000725,
},
};
@@ -430,7 +492,7 @@ export class LlamaDeuce implements LLM {
maxTokens?: number;
replicateSession: ReplicateSession;
hasStreaming: boolean;
usage: Usage;
constructor(init?: Partial<LlamaDeuce>) {
this.model = init?.model ?? "Llama-2-70b-chat-4bit";
this.chatStrategy =
@@ -445,6 +507,7 @@ export class LlamaDeuce implements LLM {
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].contextWindow; // For Replicate, the default is 500 tokens which is too low.
this.replicateSession = init?.replicateSession ?? new ReplicateSession();
this.hasStreaming = init?.hasStreaming ?? false;
this.usage = new Usage();
}
tokens(messages: ChatMessage[]): number {
@@ -616,16 +679,23 @@ If a question does not make any sense, or is not factually coherent, explain why
//TODO: Add streaming for this
//Non-streaming
const response = await this.replicateSession.replicate.run(
const response = (await this.replicateSession.replicate.run(
api,
replicateOptions,
);
)) as any;
this.usage.computeSeconds += response.metrics?.predict_time;
this.usage.cost +=
response.metrics?.predict_time *
ALL_AVAILABLE_LLAMADEUCE_MODELS[this.model].costPerSecond;
return {
message: {
content: (response as Array<string>).join("").trimStart(),
//^ We need to do this because Replicate returns a list of strings (for streaming functionality which is not exposed by the run function)
role: "assistant",
},
metrics: response.metrics,
} as R;
}
@@ -639,8 +709,12 @@ If a question does not make any sense, or is not factually coherent, explain why
export const ALL_AVAILABLE_ANTHROPIC_MODELS = {
// both models have 100k context window, see https://docs.anthropic.com/claude/reference/selecting-a-model
"claude-2": { contextWindow: 200000 },
"claude-instant-1": { contextWindow: 100000 },
"claude-2": { contextWindow: 200000, promptCost: 8.0, completionCost: 24.0 },
"claude-instant-1": {
contextWindow: 100000,
promptCost: 0.8, // for 1 Million tokens
completionCost: 2.4, // for 1 Million tokens
},
};
/**
@@ -664,6 +738,7 @@ export class Anthropic implements LLM {
callbackManager?: CallbackManager;
usage: Usage;
constructor(init?: Partial<Anthropic>) {
this.model = init?.model ?? "claude-2";
this.temperature = init?.temperature ?? 0.1;
@@ -681,6 +756,7 @@ export class Anthropic implements LLM {
timeout: this.timeout,
});
this.usage = new Usage();
this.callbackManager = init?.callbackManager;
}
@@ -809,6 +885,7 @@ export class Portkey implements LLM {
session: PortkeySession;
callbackManager?: CallbackManager;
usage: Usage;
constructor(init?: Partial<Portkey>) {
this.apiKey = init?.apiKey;
this.baseURL = init?.baseURL;
@@ -821,6 +898,8 @@ export class Portkey implements LLM {
mode: this.mode,
});
this.callbackManager = init?.callbackManager;
this.usage = new Usage();
}
tokens(messages: ChatMessage[]): number {
+143
View File
@@ -0,0 +1,143 @@
import {
AssemblyAI,
BaseServiceParams,
SubtitleFormat,
TranscribeParams,
TranscriptParagraph,
TranscriptSentence,
} from "assemblyai";
import { Document } from "../Node";
import { BaseReader } from "./base";
type AssemblyAIOptions = Partial<BaseServiceParams>;
/**
* Base class for AssemblyAI Readers.
*/
abstract class AssemblyAIReader implements BaseReader {
protected client: AssemblyAI;
/**
* Creates a new AssemblyAI Reader.
* @param assemblyAIOptions The options to configure the AssemblyAI Reader.
* Configure the `assemblyAIOptions.apiKey` with your AssemblyAI API key, or configure it as the `ASSEMBLYAI_API_KEY` environment variable.
*/
constructor(assemblyAIOptions?: AssemblyAIOptions) {
let options = assemblyAIOptions;
if (!options) {
options = {};
}
if (!options.apiKey) {
options.apiKey = process.env.ASSEMBLYAI_API_KEY;
}
if (!options.apiKey) {
throw new Error(
"No AssemblyAI API key provided. Pass an `apiKey` option, or configure the `ASSEMBLYAI_API_KEY` environment variable.",
);
}
this.client = new AssemblyAI(options as BaseServiceParams);
}
abstract loadData(...args: any[]): Promise<Document[]>;
protected async transcribeOrGetTranscript(params: TranscribeParams | string) {
if (typeof params === "string") {
return await this.client.transcripts.get(params);
} else {
return await this.client.transcripts.transcribe(params);
}
}
protected async getTranscriptId(params: TranscribeParams | string) {
if (typeof params === "string") {
return params;
} else {
return (await this.client.transcripts.transcribe(params)).id;
}
}
}
/**
* Transcribe audio and read the transcript as a document using AssemblyAI.
*/
class AudioTranscriptReader extends AssemblyAIReader {
/**
* Transcribe audio or get a transcript and load the transcript as a document using AssemblyAI.
* @param params Parameters to transcribe an audio file or get an existing transcript.
* @returns A promise that resolves to a single document containing the transcript text.
*/
async loadData(params: TranscribeParams | string): Promise<Document[]> {
const transcript = await this.transcribeOrGetTranscript(params);
return [new Document({ text: transcript.text || undefined })];
}
}
/**
* Transcribe audio and return a document for each paragraph.
*/
class AudioTranscriptParagraphsReader extends AssemblyAIReader {
/**
* Transcribe audio or get a transcript, and returns a document for each paragraph.
* @param params The parameters to transcribe audio or get an existing transcript.
* @returns A promise that resolves to an array of documents, each containing a paragraph of the transcript.
*/
async loadData(params: TranscribeParams | string): Promise<Document[]> {
let transcriptId = await this.getTranscriptId(params);
const paragraphsResponse =
await this.client.transcripts.paragraphs(transcriptId);
return paragraphsResponse.paragraphs.map(
(p: TranscriptParagraph) => new Document({ text: p.text }),
);
}
}
/**
* Transcribe audio and return a document for each sentence.
*/
class AudioTranscriptSentencesReader extends AssemblyAIReader {
/**
* Transcribe audio or get a transcript, and returns a document for each sentence.
* @param params The parameters to transcribe audio or get an existing transcript.
* @returns A promise that resolves to an array of documents, each containing a sentence of the transcript.
*/
async loadData(params: TranscribeParams | string): Promise<Document[]> {
let transcriptId = await this.getTranscriptId(params);
const sentencesResponse =
await this.client.transcripts.sentences(transcriptId);
return sentencesResponse.sentences.map(
(p: TranscriptSentence) => new Document({ text: p.text }),
);
}
}
/**
* Transcribe audio a transcript and read subtitles for the transcript as `srt` or `vtt` format.
*/
class AudioSubtitlesReader extends AssemblyAIReader {
/**
* Transcribe audio or get a transcript and reads subtitles for the transcript as `srt` or `vtt` format.
* @param params The parameters to transcribe audio or get an existing transcript.
* @param subtitleFormat The format of the subtitles, either `srt` or `vtt`.
* @returns A promise that resolves a document containing the subtitles as the page content.
*/
async loadData(
params: TranscribeParams | string,
subtitleFormat: SubtitleFormat = "srt",
): Promise<Document[]> {
let transcriptId = await this.getTranscriptId(params);
const subtitles = await this.client.transcripts.subtitles(
transcriptId,
subtitleFormat,
);
return [new Document({ text: subtitles })];
}
}
export {
AudioSubtitlesReader,
AudioTranscriptParagraphsReader,
AudioTranscriptReader,
AudioTranscriptSentencesReader,
};
export type { AssemblyAIOptions, SubtitleFormat, TranscribeParams };
+3 -1
View File
@@ -31,6 +31,7 @@ export async function createApp({
frontend,
openAIKey,
model,
communityProjectPath,
}: InstallAppArgs): Promise<void> {
const root = path.resolve(appPath);
@@ -69,6 +70,7 @@ export async function createApp({
eslint,
openAIKey,
model,
communityProjectPath,
};
if (frontend) {
@@ -106,7 +108,7 @@ export async function createApp({
console.log(
`Now have a look at the ${terminalLink(
"README.md",
`file://${appName}/README.md`,
`file://${root}/README.md`,
)} and learn how to get started.`,
);
console.log();
@@ -0,0 +1,2 @@
export const COMMUNITY_OWNER = "run-llama";
export const COMMUNITY_REPO = "create_llama_projects";
+63
View File
@@ -0,0 +1,63 @@
import { createWriteStream, promises } from "fs";
import got from "got";
import { tmpdir } from "os";
import { join } from "path";
import { Stream } from "stream";
import tar from "tar";
import { promisify } from "util";
import { makeDir } from "./make-dir";
export type RepoInfo = {
username: string;
name: string;
branch: string;
filePath: string;
};
const pipeline = promisify(Stream.pipeline);
async function downloadTar(url: string) {
const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`);
await pipeline(got.stream(url), createWriteStream(tempFile));
return tempFile;
}
export async function downloadAndExtractRepo(
root: string,
{ username, name, branch, filePath }: RepoInfo,
) {
await makeDir(root);
const tempFile = await downloadTar(
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,
);
await tar.x({
file: tempFile,
cwd: root,
strip: filePath ? filePath.split("/").length + 1 : 1,
filter: (p) =>
p.startsWith(
`${name}-${branch.replace(/\//g, "-")}${
filePath ? `/${filePath}/` : "/"
}`,
),
});
await promises.unlink(tempFile);
}
export async function getRepoRootFolders(
owner: string,
repo: string,
): Promise<string[]> {
const url = `https://api.github.com/repos/${owner}/${repo}/contents`;
const response = await got(url, {
responseType: "json",
});
const data = response.body as any[];
const folders = data.filter((item) => item.type === "dir");
return folders.map((item) => item.name);
}
+6 -227
View File
@@ -1,18 +1,18 @@
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
import ciInfo from "ci-info";
import Commander from "commander";
import Conf from "conf";
import fs from "fs";
import path from "path";
import { blue, bold, cyan, green, red, yellow } from "picocolors";
import { bold, cyan, green, red, yellow } from "picocolors";
import prompts from "prompts";
import checkForUpdate from "update-check";
import { InstallAppArgs, createApp } from "./create-app";
import { createApp } from "./create-app";
import { getPkgManager } from "./helpers/get-pkg-manager";
import { isFolderEmpty } from "./helpers/is-folder-empty";
import { validateNpmName } from "./helpers/validate-pkg";
import packageJson from "./package.json";
import { QuestionArgs, askQuestions, onPromptState } from "./questions";
let projectPath: string = "";
@@ -21,16 +21,6 @@ const handleSigTerm = () => process.exit(0);
process.on("SIGINT", handleSigTerm);
process.on("SIGTERM", handleSigTerm);
const onPromptState = (state: any) => {
if (state.aborted) {
// If we don't re-enable the terminal cursor before exiting
// the program, the cursor will remain hidden
process.stdout.write("\x1B[?25h");
process.stdout.write("\n");
process.exit(1);
}
};
const program = new Commander.Command(packageJson.name)
.version(packageJson.version)
.arguments("<project-directory>")
@@ -155,220 +145,8 @@ async function run(): Promise<void> {
process.exit(1);
}
// TODO: use Args also for program
type Args = Omit<InstallAppArgs, "appPath" | "packageManager">;
const preferences = (conf.get("preferences") || {}) as Args;
const defaults: Args = {
template: "streaming",
framework: "nextjs",
engine: "simple",
ui: "html",
eslint: true,
frontend: false,
openAIKey: "",
model: "gpt-3.5-turbo",
};
const getPrefOrDefault = (field: keyof Args) =>
preferences[field] ?? defaults[field];
const handlers = {
onCancel: () => {
console.error("Exiting.");
process.exit(1);
},
};
if (!program.framework) {
if (ciInfo.isCI) {
program.framework = getPrefOrDefault("framework");
} else {
const { framework } = await prompts(
{
type: "select",
name: "framework",
message: "Which framework would you like to use?",
choices: [
{ title: "NextJS", value: "nextjs" },
{ title: "Express", value: "express" },
{ title: "FastAPI (Python)", value: "fastapi" },
],
initial: 0,
},
handlers,
);
program.framework = framework;
preferences.framework = framework;
}
}
if (program.framework === "nextjs") {
program.template = "streaming";
}
if (!program.template) {
if (ciInfo.isCI) {
program.template = getPrefOrDefault("template");
} else {
const { template } = await prompts(
{
type: "select",
name: "template",
message: "Which template would you like to use?",
choices: [
{ title: "Chat without streaming", value: "simple" },
{ title: "Chat with streaming", value: "streaming" },
],
initial: 1,
},
handlers,
);
program.template = template;
preferences.template = template;
}
}
if (program.framework === "express" || program.framework === "fastapi") {
// if a backend-only framework is selected, ask whether we should create a frontend
if (!program.frontend) {
if (ciInfo.isCI) {
program.frontend = getPrefOrDefault("frontend");
} else {
const styledNextJS = blue("NextJS");
const styledBackend = green(
program.framework === "express"
? "Express "
: program.framework === "fastapi"
? "FastAPI (Python) "
: "",
);
const { frontend } = await prompts({
onState: onPromptState,
type: "toggle",
name: "frontend",
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
initial: getPrefOrDefault("frontend"),
active: "Yes",
inactive: "No",
});
program.frontend = Boolean(frontend);
preferences.frontend = Boolean(frontend);
}
}
}
if (program.framework === "nextjs" || program.frontend) {
if (!program.ui) {
if (ciInfo.isCI) {
program.ui = getPrefOrDefault("ui");
} else {
const { ui } = await prompts(
{
type: "select",
name: "ui",
message: "Which UI would you like to use?",
choices: [
{ title: "Just HTML", value: "html" },
{ title: "Shadcn", value: "shadcn" },
],
initial: 0,
},
handlers,
);
program.ui = ui;
preferences.ui = ui;
}
}
}
if (program.framework === "nextjs") {
if (!program.model) {
if (ciInfo.isCI) {
program.model = getPrefOrDefault("model");
} else {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which model would you like to use?",
choices: [
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo" },
{ title: "gpt-4", value: "gpt-4" },
{ title: "gpt-4-1106-preview", value: "gpt-4-1106-preview" },
{ title: "gpt-4-vision-preview", value: "gpt-4-vision-preview" },
],
initial: 0,
},
handlers,
);
program.model = model;
preferences.model = model;
}
}
}
if (program.framework === "express" || program.framework === "nextjs") {
if (!program.engine) {
if (ciInfo.isCI) {
program.engine = getPrefOrDefault("engine");
} else {
const { engine } = await prompts(
{
type: "select",
name: "engine",
message: "Which chat engine would you like to use?",
choices: [
{ title: "ContextChatEngine", value: "context" },
{
title: "SimpleChatEngine (no data, just chat)",
value: "simple",
},
],
initial: 0,
},
handlers,
);
program.engine = engine;
preferences.engine = engine;
}
}
}
if (!program.openAIKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message: "Please provide your OpenAI API key (leave blank to skip):",
},
handlers,
);
program.openAIKey = key;
preferences.openAIKey = key;
}
if (
program.framework !== "fastapi" &&
!process.argv.includes("--eslint") &&
!process.argv.includes("--no-eslint")
) {
if (ciInfo.isCI) {
program.eslint = getPrefOrDefault("eslint");
} else {
const styledEslint = blue("ESLint");
const { eslint } = await prompts({
onState: onPromptState,
type: "toggle",
name: "eslint",
message: `Would you like to use ${styledEslint}?`,
initial: getPrefOrDefault("eslint"),
active: "Yes",
inactive: "No",
});
program.eslint = Boolean(eslint);
preferences.eslint = Boolean(eslint);
}
}
const preferences = (conf.get("preferences") || {}) as QuestionArgs;
await askQuestions(program as unknown as QuestionArgs, preferences);
await createApp({
template: program.template,
@@ -381,6 +159,7 @@ async function run(): Promise<void> {
frontend: program.frontend,
openAIKey: program.openAIKey,
model: program.model,
communityProjectPath: program.communityProjectPath,
});
conf.set("preferences", preferences);
}
+277
View File
@@ -0,0 +1,277 @@
import ciInfo from "ci-info";
import { blue, green } from "picocolors";
import prompts from "prompts";
import { InstallAppArgs } from "./create-app";
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant";
import { getRepoRootFolders } from "./helpers/repo";
export type QuestionArgs = Omit<InstallAppArgs, "appPath" | "packageManager">;
const defaults: QuestionArgs = {
template: "streaming",
framework: "nextjs",
engine: "simple",
ui: "html",
eslint: true,
frontend: false,
openAIKey: "",
model: "gpt-3.5-turbo",
communityProjectPath: "",
};
const handlers = {
onCancel: () => {
console.error("Exiting.");
process.exit(1);
},
};
export const onPromptState = (state: any) => {
if (state.aborted) {
// If we don't re-enable the terminal cursor before exiting
// the program, the cursor will remain hidden
process.stdout.write("\x1B[?25h");
process.stdout.write("\n");
process.exit(1);
}
};
export const askQuestions = async (
program: QuestionArgs,
preferences: QuestionArgs,
) => {
const getPrefOrDefault = <K extends keyof QuestionArgs>(
field: K,
): QuestionArgs[K] => preferences[field] ?? defaults[field];
if (!program.template) {
if (ciInfo.isCI) {
program.template = getPrefOrDefault("template");
} else {
const styledRepo = blue(
`https://github.com/${COMMUNITY_OWNER}/${COMMUNITY_REPO}`,
);
const { template } = await prompts(
{
type: "select",
name: "template",
message: "Which template would you like to use?",
choices: [
{ title: "Chat without streaming", value: "simple" },
{ title: "Chat with streaming", value: "streaming" },
{
title: `Community template from ${styledRepo}`,
value: "community",
},
],
initial: 1,
},
handlers,
);
program.template = template;
preferences.template = template;
}
}
if (program.template === "community") {
const rootFolderNames = await getRepoRootFolders(
COMMUNITY_OWNER,
COMMUNITY_REPO,
);
const { communityProjectPath } = await prompts(
{
type: "select",
name: "communityProjectPath",
message: "Select community template",
choices: rootFolderNames.map((name) => ({
title: name,
value: name,
})),
initial: 0,
},
{
onCancel: () => {
console.error("Exiting.");
process.exit(1);
},
},
);
program.communityProjectPath = communityProjectPath;
preferences.communityProjectPath = communityProjectPath;
return; // early return - no further questions needed for community projects
}
if (!program.framework) {
if (ciInfo.isCI) {
program.framework = getPrefOrDefault("framework");
} else {
const choices = [
{ title: "Express", value: "express" },
{ title: "FastAPI (Python)", value: "fastapi" },
];
if (program.template === "streaming") {
// allow NextJS only for streaming template
choices.unshift({ title: "NextJS", value: "nextjs" });
}
const { framework } = await prompts(
{
type: "select",
name: "framework",
message: "Which framework would you like to use?",
choices,
initial: 0,
},
handlers,
);
program.framework = framework;
preferences.framework = framework;
}
}
if (program.framework === "express" || program.framework === "fastapi") {
// if a backend-only framework is selected, ask whether we should create a frontend
if (!program.frontend) {
if (ciInfo.isCI) {
program.frontend = getPrefOrDefault("frontend");
} else {
const styledNextJS = blue("NextJS");
const styledBackend = green(
program.framework === "express"
? "Express "
: program.framework === "fastapi"
? "FastAPI (Python) "
: "",
);
const { frontend } = await prompts({
onState: onPromptState,
type: "toggle",
name: "frontend",
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
initial: getPrefOrDefault("frontend"),
active: "Yes",
inactive: "No",
});
program.frontend = Boolean(frontend);
preferences.frontend = Boolean(frontend);
}
}
}
if (program.framework === "nextjs" || program.frontend) {
if (!program.ui) {
if (ciInfo.isCI) {
program.ui = getPrefOrDefault("ui");
} else {
const { ui } = await prompts(
{
type: "select",
name: "ui",
message: "Which UI would you like to use?",
choices: [
{ title: "Just HTML", value: "html" },
{ title: "Shadcn", value: "shadcn" },
],
initial: 0,
},
handlers,
);
program.ui = ui;
preferences.ui = ui;
}
}
}
if (program.framework === "nextjs") {
if (!program.model) {
if (ciInfo.isCI) {
program.model = getPrefOrDefault("model");
} else {
const { model } = await prompts(
{
type: "select",
name: "model",
message: "Which model would you like to use?",
choices: [
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo" },
{ title: "gpt-4", value: "gpt-4" },
{ title: "gpt-4-1106-preview", value: "gpt-4-1106-preview" },
{
title: "gpt-4-vision-preview",
value: "gpt-4-vision-preview",
},
],
initial: 0,
},
handlers,
);
program.model = model;
preferences.model = model;
}
}
}
if (program.framework === "express" || program.framework === "nextjs") {
if (!program.engine) {
if (ciInfo.isCI) {
program.engine = getPrefOrDefault("engine");
} else {
const { engine } = await prompts(
{
type: "select",
name: "engine",
message: "Which chat engine would you like to use?",
choices: [
{ title: "ContextChatEngine", value: "context" },
{
title: "SimpleChatEngine (no data, just chat)",
value: "simple",
},
],
initial: 0,
},
handlers,
);
program.engine = engine;
preferences.engine = engine;
}
}
}
if (!program.openAIKey) {
const { key } = await prompts(
{
type: "text",
name: "key",
message: "Please provide your OpenAI API key (leave blank to skip):",
},
handlers,
);
program.openAIKey = key;
preferences.openAIKey = key;
}
if (
program.framework !== "fastapi" &&
!process.argv.includes("--eslint") &&
!process.argv.includes("--no-eslint")
) {
if (ciInfo.isCI) {
program.eslint = getPrefOrDefault("eslint");
} else {
const styledEslint = blue("ESLint");
const { eslint } = await prompts({
onState: onPromptState,
type: "toggle",
name: "eslint",
message: `Would you like to use ${styledEslint}?`,
initial: getPrefOrDefault("eslint"),
active: "Yes",
inactive: "No",
});
program.eslint = Boolean(eslint);
preferences.eslint = Boolean(eslint);
}
}
};
+21
View File
@@ -7,7 +7,9 @@ import path from "path";
import { bold, cyan } from "picocolors";
import { version } from "../../core/package.json";
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "../helpers/constant";
import { PackageManager } from "../helpers/get-pkg-manager";
import { downloadAndExtractRepo } from "../helpers/repo";
import {
InstallTemplateArgs,
TemplateEngine,
@@ -306,10 +308,29 @@ const installPythonTemplate = async ({
);
};
const installCommunityProject = async ({
root,
communityProjectPath,
}: Pick<InstallTemplateArgs, "root" | "communityProjectPath">) => {
console.log("\nInstalling community project:", communityProjectPath!);
await downloadAndExtractRepo(root, {
username: COMMUNITY_OWNER,
name: COMMUNITY_REPO,
branch: "main",
filePath: communityProjectPath!,
});
};
export const installTemplate = async (
props: InstallTemplateArgs & { backend: boolean },
) => {
process.chdir(props.root);
if (props.template === "community" && props.communityProjectPath) {
await installCommunityProject(props);
return;
}
if (props.framework === "fastapi") {
await installPythonTemplate(props);
} else {
+2 -1
View File
@@ -1,6 +1,6 @@
import { PackageManager } from "../helpers/get-pkg-manager";
export type TemplateType = "simple" | "streaming";
export type TemplateType = "simple" | "streaming" | "community";
export type TemplateFramework = "nextjs" | "express" | "fastapi";
export type TemplateEngine = "simple" | "context";
export type TemplateUI = "html" | "shadcn";
@@ -19,4 +19,5 @@ export interface InstallTemplateArgs {
openAIKey?: string;
forBackend?: string;
model: string;
communityProjectPath?: string;
}
+1
View File
@@ -9,6 +9,7 @@ module.exports = {
"OPENAI_API_KEY",
"REPLICATE_API_TOKEN",
"ANTHROPIC_API_KEY",
"ASSEMBLYAI_API_KEY",
"AZURE_OPENAI_KEY",
"AZURE_OPENAI_ENDPOINT",
+18 -117
View File
@@ -104,25 +104,6 @@ importers:
specifier: ^4.9.5
version: 4.9.5
apps/mongodb:
dependencies:
dotenv:
specifier: ^16.3.1
version: 16.3.1
llamaindex:
specifier: workspace:*
version: link:../../packages/core
mongodb:
specifier: ^6.2.0
version: 6.2.0
devDependencies:
'@types/node':
specifier: ^18.18.6
version: 18.18.8
ts-node:
specifier: ^10.9.1
version: 10.9.1(@types/node@18.18.8)(typescript@5.3.2)
examples:
dependencies:
'@notionhq/client':
@@ -134,9 +115,15 @@ importers:
commander:
specifier: ^11.1.0
version: 11.1.0
dotenv:
specifier: ^16.3.1
version: 16.3.1
llamaindex:
specifier: latest
version: link:../packages/core
mongodb:
specifier: ^6.2.0
version: 6.3.0
devDependencies:
'@types/node':
specifier: ^18.18.6
@@ -156,6 +143,9 @@ importers:
'@xenova/transformers':
specifier: ^2.8.0
version: 2.8.0
assemblyai:
specifier: ^3.1.3
version: 3.1.3
crypto-js:
specifier: ^4.2.0
version: 4.2.0
@@ -4461,12 +4451,6 @@ packages:
dependencies:
undici-types: 5.26.5
/@types/node@18.18.8:
resolution: {integrity: sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==}
dependencies:
undici-types: 5.26.5
dev: true
/@types/node@20.9.0:
resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==}
dependencies:
@@ -4641,13 +4625,6 @@ packages:
'@types/webidl-conversions': 7.0.2
dev: false
/@types/whatwg-url@8.2.2:
resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
dependencies:
'@types/node': 18.18.12
'@types/webidl-conversions': 7.0.2
dev: false
/@types/ws@8.5.6:
resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
dependencies:
@@ -5175,6 +5152,15 @@ packages:
safer-buffer: 2.1.2
dev: true
/assemblyai@3.1.3:
resolution: {integrity: sha512-MOVibx4jcKk48lUKoLQWCAnWzm8cBL99GnQ7Af/2XTkGBVUCefocjIO5kJWqRdwLAdoD1D0csR+l4ll62i9vyQ==}
dependencies:
ws: 8.14.2
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: false
/assert@2.1.0:
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
dependencies:
@@ -10940,13 +10926,6 @@ packages:
hasBin: true
dev: true
/mongodb-connection-string-url@2.6.0:
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
dependencies:
'@types/whatwg-url': 8.2.2
whatwg-url: 11.0.0
dev: false
/mongodb-connection-string-url@3.0.0:
resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==}
dependencies:
@@ -10954,38 +10933,6 @@ packages:
whatwg-url: 13.0.0
dev: false
/mongodb@6.2.0:
resolution: {integrity: sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==}
engines: {node: '>=16.20.1'}
peerDependencies:
'@aws-sdk/credential-providers': ^3.188.0
'@mongodb-js/zstd': ^1.1.0
gcp-metadata: ^5.2.0
kerberos: ^2.0.1
mongodb-client-encryption: '>=6.0.0 <7'
snappy: ^7.2.2
socks: ^2.7.1
peerDependenciesMeta:
'@aws-sdk/credential-providers':
optional: true
'@mongodb-js/zstd':
optional: true
gcp-metadata:
optional: true
kerberos:
optional: true
mongodb-client-encryption:
optional: true
snappy:
optional: true
socks:
optional: true
dependencies:
'@mongodb-js/saslprep': 1.1.1
bson: 6.2.0
mongodb-connection-string-url: 2.6.0
dev: false
/mongodb@6.3.0:
resolution: {integrity: sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==}
engines: {node: '>=16.20.1'}
@@ -14615,13 +14562,6 @@ packages:
punycode: 2.3.1
dev: true
/tr46@3.0.0:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'}
dependencies:
punycode: 2.3.1
dev: false
/tr46@4.1.1:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
engines: {node: '>=14'}
@@ -14721,37 +14661,6 @@ packages:
yn: 3.1.1
dev: true
/ts-node@10.9.1(@types/node@18.18.8)(typescript@5.3.2):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.9
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 18.18.8
acorn: 8.11.2
acorn-walk: 8.3.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.3.2
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/tsconfig-paths@3.14.2:
resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
dependencies:
@@ -15710,14 +15619,6 @@ packages:
engines: {node: '>=0.8.0'}
dev: false
/whatwg-url@11.0.0:
resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
engines: {node: '>=12'}
dependencies:
tr46: 3.0.0
webidl-conversions: 7.0.0
dev: false
/whatwg-url@13.0.0:
resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==}
engines: {node: '>=16'}