From 689245f3f18090f6dbb957e57be58804dc933bb2 Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Tue, 12 Nov 2024 18:33:20 -0800 Subject: [PATCH 1/4] [BE] Add script to dump all file contents into one file so it is ease to work with LLMs for code gen / debugging --- .gitignore | 4 +++- dump_file_contents.sh | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100755 dump_file_contents.sh diff --git a/.gitignore b/.gitignore index 3e8ec232c8..6ed4099c71 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ characters/ packages/core/src/providers/cache packages/core/src/providers/cache/* -cache/* \ No newline at end of file +cache/* + +all_files_content.txt diff --git a/dump_file_contents.sh b/dump_file_contents.sh new file mode 100755 index 0000000000..e24346bdfd --- /dev/null +++ b/dump_file_contents.sh @@ -0,0 +1,35 @@ +#!/bin/zsh +# Define the output file +OUTPUT_FILE="all_files_content.txt" +# Remove the output file if it already exists to start fresh +if [[ -f "$OUTPUT_FILE" ]]; then + rm "$OUTPUT_FILE" +fi +# Start the find command, excluding the output file and hidden files +FIND_CMD="find . -type f ! -name \"$OUTPUT_FILE\" ! -path '*/\.*'" +# Loop through command-line arguments to add exclusions for file extensions, specific files, or directories +for arg in "$@"; do + if [[ "$arg" == \.* ]]; then + # If the argument is a file extension, exclude files with that extension + FIND_CMD+=" ! -iname '*$arg'" + elif [[ -d "$arg" ]]; then + # If the argument is a directory, exclude it and its contents + FIND_CMD+=" ! -path './$arg/*'" + elif [[ -f "$arg" ]]; then + # If the argument is a file, exclude it + FIND_CMD+=" ! -name '$arg'" + else + # If the argument is a wildcard pattern, exclude matching files + FIND_CMD+=" ! -name '$arg'" + fi +done +# Execute the constructed find command and process files +eval $FIND_CMD | while IFS= read -r file; do + # Append the file name and its content to the output file with separators + echo "---" >> "$OUTPUT_FILE" + echo "$file" >> "$OUTPUT_FILE" + echo "---" >> "$OUTPUT_FILE" + cat "$file" >> "$OUTPUT_FILE" + echo "\n\n" >> "$OUTPUT_FILE" +done +echo "All contents have been written to $OUTPUT_FILE" From e9d03e73d1b77224d02cbcf18925be881fa1ea3e Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Wed, 13 Nov 2024 21:51:24 -0800 Subject: [PATCH 2/4] [Refactor][1/2] Implement generateObject for typesafe JSON output --- package.json | 3 +- packages/core/package.json | 1 + packages/core/src/generation.ts | 387 ++++++++++++++++++++- packages/core/src/tests/generation.test.ts | 166 +++++++++ pnpm-lock.yaml | 13 +- 5 files changed, 560 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/tests/generation.test.ts diff --git a/package.json b/package.json index 282297c4c2..a8265ac642 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,6 @@ "ollama-ai-provider": "^0.16.1", "optional": "^0.1.4", "sharp": "^0.33.5" - } + }, + "packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee" } diff --git a/packages/core/package.json b/packages/core/package.json index 2e4a63deab..0c3312ba29 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,6 +26,7 @@ "@rollup/plugin-typescript": "11.1.6", "@types/fluent-ffmpeg": "2.1.27", "@types/jest": "29.5.14", + "@types/mocha": "^10.0.9", "@types/node": "22.8.4", "@types/pdfjs-dist": "^2.10.378", "@types/tar": "6.1.13", diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts index 14c7ccc3bc..9e6b3d3f0f 100644 --- a/packages/core/src/generation.ts +++ b/packages/core/src/generation.ts @@ -3,7 +3,11 @@ import { createGroq } from "@ai-sdk/groq"; import { createOpenAI } from "@ai-sdk/openai"; import { getModel } from "./models.ts"; import { IImageDescriptionService, ModelClass } from "./types.ts"; -import { generateText as aiGenerateText } from "ai"; +import { + generateText as aiGenerateText, + generateObject as aiGenerateObject, + GenerateObjectResult, +} from "ai"; import { Buffer } from "buffer"; import { createOllama } from "ollama-ai-provider"; import OpenAI from "openai"; @@ -26,6 +30,7 @@ import { ModelProviderName, ServiceType, } from "./types.ts"; +import { ZodSchema } from "zod"; /** * Send a message to the model for a text generateText - receive a string back and parse how you'd like @@ -105,8 +110,8 @@ export async function generateText({ break; } - case ModelProviderName.GOOGLE: - { const google = createGoogleGenerativeAI(); + case ModelProviderName.GOOGLE: { + const google = createGoogleGenerativeAI(); const { text: anthropicResponse } = await aiGenerateText({ model: google(model), @@ -122,7 +127,8 @@ export async function generateText({ }); response = anthropicResponse; - break; } + break; + } case ModelProviderName.ANTHROPIC: { elizaLogger.debug("Initializing Anthropic model."); @@ -194,7 +200,9 @@ export async function generateText({ } case ModelProviderName.LLAMALOCAL: { - elizaLogger.debug("Using local Llama model for text completion."); + elizaLogger.debug( + "Using local Llama model for text completion." + ); response = await runtime .getService( ServiceType.TEXT_GENERATION @@ -396,8 +404,8 @@ export async function splitChunks( console.log("model", model); console.log("model.model.embedding", model.model.embedding); - - if(!model.model.embedding) { + + if (!model.model.embedding) { throw new Error("Model does not support embedding"); } @@ -755,3 +763,368 @@ export const generateCaption = async ( description: resp.description.trim(), }; }; + +/** + * Configuration options for generating objects with a model. + */ +export interface GenerationOptions { + runtime: IAgentRuntime; + context: string; + modelClass: ModelClass; + schema?: ZodSchema; + schemaName?: string; + schemaDescription?: string; + stop?: string[]; + mode?: "auto" | "json" | "tool"; + experimental_providerMetadata?: Record; +} + +/** + * Base settings for model generation. + */ +interface ModelSettings { + prompt: string; + temperature: number; + maxTokens: number; + frequencyPenalty: number; + presencePenalty: number; + stop?: string[]; +} + +/** + * Generates structured objects from a prompt using specified AI models and configuration options. + * + * @param {GenerationOptions} options - Configuration options for generating objects. + * @returns {Promise} - A promise that resolves to an array of generated objects. + * @throws {Error} - Throws an error if the provider is unsupported or if generation fails. + */ +export const generateObjectV2 = async ({ + runtime, + context, + modelClass, + schema, + schemaName, + schemaDescription, + stop, + mode = "json", +}: GenerationOptions): Promise> => { + if (!context) { + const errorMessage = "generateObject context is empty"; + console.error(errorMessage); + throw new Error(errorMessage); + } + + const provider = runtime.modelProvider; + const model = models[provider].model[modelClass]; + const temperature = models[provider].settings.temperature; + const frequency_penalty = models[provider].settings.frequency_penalty; + const presence_penalty = models[provider].settings.presence_penalty; + const max_context_length = models[provider].settings.maxInputTokens; + const max_response_length = models[provider].settings.maxOutputTokens; + const apiKey = runtime.token; + + try { + context = await trimTokens(context, max_context_length, modelClass); + + const modelOptions: ModelSettings = { + prompt: context, + temperature, + maxTokens: max_response_length, + frequencyPenalty: frequency_penalty, + presencePenalty: presence_penalty, + stop: stop || models[provider].settings.stop, + }; + + const response = await handleProvider({ + provider, + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, + runtime, + context, + modelClass, + }); + + return response; + } catch (error) { + console.error("Error in generateObject:", error); + throw error; + } +}; + +/** + * Interface for provider-specific generation options. + */ +interface ProviderOptions { + runtime: IAgentRuntime; + provider: ModelProviderName; + model: any; + apiKey: string; + schema?: ZodSchema; + schemaName?: string; + schemaDescription?: string; + mode?: "auto" | "json" | "tool"; + experimental_providerMetadata?: Record; + modelOptions: ModelSettings; + modelClass: string; + context: string; +} + +/** + * Handles AI generation based on the specified provider. + * + * @param {ProviderOptions} options - Configuration options specific to the provider. + * @returns {Promise} - A promise that resolves to an array of generated objects. + */ +export async function handleProvider( + options: ProviderOptions +): Promise> { + const { provider, runtime, context, modelClass } = options; + switch (provider) { + case ModelProviderName.OPENAI: + case ModelProviderName.LLAMACLOUD: + return await handleOpenAI(options); + case ModelProviderName.ANTHROPIC: + return await handleAnthropic(options); + case ModelProviderName.GROK: + return await handleGrok(options); + case ModelProviderName.GROQ: + return await handleGroq(options); + case ModelProviderName.LLAMALOCAL: + return await generateObject({ + runtime, + context, + modelClass, + }); + case ModelProviderName.GOOGLE: + return await handleGoogle(options); + case ModelProviderName.REDPILL: + return await handleRedPill(options); + case ModelProviderName.OPENROUTER: + return await handleOpenRouter(options); + case ModelProviderName.OLLAMA: + return await handleOllama(options); + default: { + const errorMessage = `Unsupported provider: ${provider}`; + elizaLogger.error(errorMessage); + throw new Error(errorMessage); + } + } +} +/** + * Handles object generation for OpenAI. + * + * @param {ProviderOptions} options - Options specific to OpenAI. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleOpenAI({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const openai = createOpenAI({ apiKey }); + return await aiGenerateObject({ + model: openai.languageModel(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Anthropic models. + * + * @param {ProviderOptions} options - Options specific to Anthropic. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleAnthropic({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const anthropic = createAnthropic({ apiKey }); + return await aiGenerateObject({ + model: anthropic.languageModel(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Grok models. + * + * @param {ProviderOptions} options - Options specific to Grok. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleGrok({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const grok = createOpenAI({ apiKey, baseURL: models.grok.endpoint }); + return await aiGenerateObject({ + model: grok.languageModel(model, { parallelToolCalls: false }), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Groq models. + * + * @param {ProviderOptions} options - Options specific to Groq. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleGroq({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const groq = createGroq({ apiKey }); + return await aiGenerateObject({ + model: groq.languageModel(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Google models. + * + * @param {ProviderOptions} options - Options specific to Google. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleGoogle({ + model, + apiKey: _apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const google = createGoogleGenerativeAI(); + return await aiGenerateObject({ + model: google(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Redpill models. + * + * @param {ProviderOptions} options - Options specific to Redpill. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleRedPill({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const redPill = createOpenAI({ apiKey, baseURL: models.redpill.endpoint }); + return await aiGenerateObject({ + model: redPill.languageModel(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for OpenRouter models. + * + * @param {ProviderOptions} options - Options specific to OpenRouter. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleOpenRouter({ + model, + apiKey, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, +}: ProviderOptions): Promise> { + const openRouter = createOpenAI({ + apiKey, + baseURL: models.openrouter.endpoint, + }); + return await aiGenerateObject({ + model: openRouter.languageModel(model), + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} + +/** + * Handles object generation for Ollama models. + * + * @param {ProviderOptions} options - Options specific to Ollama. + * @returns {Promise>} - A promise that resolves to generated objects. + */ +async function handleOllama({ + model, + schema, + schemaName, + schemaDescription, + mode, + modelOptions, + provider, +}: ProviderOptions): Promise> { + const ollamaProvider = createOllama({ + baseURL: models[provider].endpoint + "/api", + }); + const ollama = ollamaProvider(model); + return await aiGenerateObject({ + model: ollama, + schema, + schemaName, + schemaDescription, + mode, + ...modelOptions, + }); +} diff --git a/packages/core/src/tests/generation.test.ts b/packages/core/src/tests/generation.test.ts new file mode 100644 index 0000000000..b523025925 --- /dev/null +++ b/packages/core/src/tests/generation.test.ts @@ -0,0 +1,166 @@ +import { + generateObject, + GenerationOptions, + trimTokens, + handleProvider, +} from "../generation"; +import { createRuntime } from "../test_resources/createRuntime"; +import { ModelProviderName, ModelClass } from "../types"; +import { ZodSchema } from "zod"; +import dotenv from "dotenv"; + +dotenv.config({ path: ".dev.vars" }); + +describe("generateObject", () => { + let runtime; + + beforeAll(async () => { + // Create runtime with a mock environment + const setup = await createRuntime({ + env: process.env as Record, + }); + runtime = setup.runtime; + }); + + test("should throw an error when context is empty", async () => { + const options: GenerationOptions = { + runtime, + context: "", + modelClass: ModelClass.SMALL, + }; + + await expect(generateObject(options)).rejects.toThrow( + "generateObject context is empty" + ); + }); + + test("should handle supported provider calls", async () => { + // Mock provider and trimTokens response + const context = "Test prompt for generation"; + const provider = ModelProviderName.OPENAI; + const schema: ZodSchema = ZodSchema.any(); // Replace with a valid schema if needed + + runtime.modelProvider = provider; + + (trimTokens as jest.Mock).mockResolvedValue(context); + (handleProvider as jest.Mock).mockResolvedValue([ + { response: "Generated text" }, + ]); + + const options: GenerationOptions = { + runtime, + context, + modelClass: ModelClass.SMALL, + schema, + schemaName: "TestSchema", + schemaDescription: "A schema for testing purposes", + mode: "json", + }; + + const result = await generateObject(options); + + expect(trimTokens).toHaveBeenCalledWith( + context, + expect.any(Number), + ModelClass.SMALL + ); + expect(handleProvider).toHaveBeenCalledWith( + expect.objectContaining({ + provider, + model: expect.anything(), + schema, + schemaName: "TestSchema", + schemaDescription: "A schema for testing purposes", + }) + ); + expect(result).toEqual([{ response: "Generated text" }]); + }); + + test("should throw an error for unsupported provider", async () => { + runtime.modelProvider = "unsupportedProvider" as ModelProviderName; + + const options: GenerationOptions = { + runtime, + context: "This should fail", + modelClass: ModelClass.SMALL, + }; + + await expect(generateObject(options)).rejects.toThrow( + "Unsupported provider" + ); + }); +}); + +describe("handleProvider", () => { + let runtime; + + beforeAll(async () => { + const setup = await createRuntime({ + env: process.env as Record, + }); + runtime = setup.runtime; + }); + + test("should handle OpenAI provider call", async () => { + const options = { + runtime, + provider: ModelProviderName.OPENAI, + model: "text-davinci-003", + apiKey: "testApiKey", + schema: ZodSchema.any(), + schemaName: "TestSchema", + schemaDescription: "A test schema", + mode: "json", + modelOptions: { + prompt: "Test prompt", + temperature: 0.7, + maxTokens: 100, + frequencyPenalty: 0, + presencePenalty: 0, + }, + modelClass: ModelClass.SMALL, + context: "This is a test context", + }; + + (handleOpenAI as jest.Mock).mockResolvedValue([ + { response: "Generated by OpenAI" }, + ]); + + const result = await handleProvider(options); + + expect(handleOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + model: "text-davinci-003", + apiKey: "testApiKey", + schemaName: "TestSchema", + }) + ); + expect(result).toEqual([{ response: "Generated by OpenAI" }]); + }); + + test("should throw error on unsupported provider in handleProvider", async () => { + const options = { + runtime, + provider: "unsupportedProvider" as ModelProviderName, + model: "unsupportedModel", + apiKey: "testApiKey", + schema: ZodSchema.any(), + schemaName: "UnsupportedSchema", + schemaDescription: "This should fail", + mode: "json", + modelOptions: { + prompt: "Test unsupported provider", + temperature: 0.7, + maxTokens: 100, + frequencyPenalty: 0, + presencePenalty: 0, + }, + modelClass: ModelClass.SMALL, + context: "This is an unsupported provider context", + }; + + await expect(handleProvider(options)).rejects.toThrow( + "Unsupported provider" + ); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb77479d94..c9a1b47803 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -459,6 +459,9 @@ importers: '@types/jest': specifier: 29.5.14 version: 29.5.14 + '@types/mocha': + specifier: ^10.0.9 + version: 10.0.9 '@types/node': specifier: 22.8.4 version: 22.8.4 @@ -512,7 +515,7 @@ importers: version: 2.79.2 ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@types/node@22.8.4)(typescript@5.6.3)))(typescript@5.6.3) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@types/node@22.8.4)(typescript@5.6.3)))(typescript@5.6.3) ts-node: specifier: 10.9.2 version: 10.9.2(@types/node@22.8.4)(typescript@5.6.3) @@ -3918,6 +3921,9 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/mocha@10.0.9': + resolution: {integrity: sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -16165,6 +16171,8 @@ snapshots: '@types/minimist@1.2.5': {} + '@types/mocha@10.0.9': {} + '@types/ms@0.7.34': {} '@types/node-fetch@2.6.11': @@ -24844,7 +24852,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@types/node@22.8.4)(typescript@5.6.3)))(typescript@5.6.3): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.8.4)(ts-node@10.9.2(@types/node@22.8.4)(typescript@5.6.3)))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -24862,6 +24870,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.0) + esbuild: 0.24.0 ts-mixer@6.0.4: {} From c1745f2532982148605038bf2d9e8c9ef7e56aed Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Wed, 13 Nov 2024 21:57:00 -0800 Subject: [PATCH 3/4] Remove unneeded file --- .gitignore | 2 -- dump_file_contents.sh | 35 ----------------------------------- 2 files changed, 37 deletions(-) delete mode 100755 dump_file_contents.sh diff --git a/.gitignore b/.gitignore index 6ed4099c71..71e424acf5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,3 @@ characters/ packages/core/src/providers/cache packages/core/src/providers/cache/* cache/* - -all_files_content.txt diff --git a/dump_file_contents.sh b/dump_file_contents.sh deleted file mode 100755 index e24346bdfd..0000000000 --- a/dump_file_contents.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/zsh -# Define the output file -OUTPUT_FILE="all_files_content.txt" -# Remove the output file if it already exists to start fresh -if [[ -f "$OUTPUT_FILE" ]]; then - rm "$OUTPUT_FILE" -fi -# Start the find command, excluding the output file and hidden files -FIND_CMD="find . -type f ! -name \"$OUTPUT_FILE\" ! -path '*/\.*'" -# Loop through command-line arguments to add exclusions for file extensions, specific files, or directories -for arg in "$@"; do - if [[ "$arg" == \.* ]]; then - # If the argument is a file extension, exclude files with that extension - FIND_CMD+=" ! -iname '*$arg'" - elif [[ -d "$arg" ]]; then - # If the argument is a directory, exclude it and its contents - FIND_CMD+=" ! -path './$arg/*'" - elif [[ -f "$arg" ]]; then - # If the argument is a file, exclude it - FIND_CMD+=" ! -name '$arg'" - else - # If the argument is a wildcard pattern, exclude matching files - FIND_CMD+=" ! -name '$arg'" - fi -done -# Execute the constructed find command and process files -eval $FIND_CMD | while IFS= read -r file; do - # Append the file name and its content to the output file with separators - echo "---" >> "$OUTPUT_FILE" - echo "$file" >> "$OUTPUT_FILE" - echo "---" >> "$OUTPUT_FILE" - cat "$file" >> "$OUTPUT_FILE" - echo "\n\n" >> "$OUTPUT_FILE" -done -echo "All contents have been written to $OUTPUT_FILE" From bb13009270f4145d93f2c19307df5fe2590b692d Mon Sep 17 00:00:00 2001 From: Monil Patel Date: Wed, 13 Nov 2024 22:06:18 -0800 Subject: [PATCH 4/4] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index e0c68d548c..71e424acf5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,3 @@ characters/ packages/core/src/providers/cache packages/core/src/providers/cache/* cache/* -packages/core/cache/*