diff --git a/packages/tools/package.json b/packages/tools/package.json
index 0e0be05cfc2a34961120e92ea8cbbd07c85525b4..c3e2117bc0bcf50dfdae77375eb528d4a1acd3e0 100644
--- a/packages/tools/package.json
+++ b/packages/tools/package.json
@@ -33,12 +33,19 @@
   "devDependencies": {
     "bunchee": "6.4.0",
     "vitest": "^2.1.5",
-    "@types/node": "^22.9.0"
+    "@types/node": "^22.9.0",
+    "@types/papaparse": "^5.3.15"
   },
   "dependencies": {
+    "@apidevtools/swagger-parser": "^10.1.0",
     "@llamaindex/core": "workspace:*",
     "@llamaindex/env": "workspace:*",
     "@e2b/code-interpreter": "^1.0.4",
+    "duck-duck-scrape": "^2.2.5",
+    "papaparse": "^5.4.1",
+    "marked": "^14.1.2",
+    "got": "^14.4.1",
+    "formdata-node": "^6.0.3",
     "ajv": "^8.12.0",
     "wikipedia": "^2.1.2",
     "zod": "^3.23.8"
diff --git a/packages/tools/src/helper.ts b/packages/tools/src/helper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7898708231f65388d3080339e9af3595df18b0a9
--- /dev/null
+++ b/packages/tools/src/helper.ts
@@ -0,0 +1,24 @@
+import fs from "node:fs";
+import path from "node:path";
+
+export async function saveDocument(filepath: string, content: string | Buffer) {
+  if (path.isAbsolute(filepath)) {
+    throw new Error("Absolute file paths are not allowed.");
+  }
+  if (!process.env.FILESERVER_URL_PREFIX) {
+    throw new Error("FILESERVER_URL_PREFIX environment variable is not set.");
+  }
+
+  const dirPath = path.dirname(filepath);
+  await fs.promises.mkdir(dirPath, { recursive: true });
+
+  if (typeof content === "string") {
+    await fs.promises.writeFile(filepath, content, "utf-8");
+  } else {
+    await fs.promises.writeFile(filepath, content);
+  }
+
+  const fileurl = `${process.env.FILESERVER_URL_PREFIX}/${filepath}`;
+  console.log(`Saved document to ${filepath}. Reachable at URL: ${fileurl}`);
+  return fileurl;
+}
diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts
index 61e6a65cbae1481aa330f4145da37396332b0e93..61827f32bb1ea6d21c9c6d8e430485258a2a6432 100644
--- a/packages/tools/src/index.ts
+++ b/packages/tools/src/index.ts
@@ -1,9 +1,9 @@
+export * from "./tools/code-generator";
 export * from "./tools/document-generator";
+export * from "./tools/duckduckgo";
+export * from "./tools/form-filling";
+export * from "./tools/img-gen";
 export * from "./tools/interpreter";
+export * from "./tools/openapi-action";
 export * from "./tools/weather";
 export * from "./tools/wiki";
-
-// Export configuration system
-export * from "./config";
-
-// TODO: export other tools
diff --git a/packages/tools/src/tools/code-generator.ts b/packages/tools/src/tools/code-generator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e98a7e76277be435ee723b6553b5122456e92d1e
--- /dev/null
+++ b/packages/tools/src/tools/code-generator.ts
@@ -0,0 +1,115 @@
+import { Settings, type JSONValue } from "@llamaindex/core/global";
+import type { ChatMessage } from "@llamaindex/core/llms";
+import { tool } from "@llamaindex/core/tools";
+import { z } from "zod";
+
+// prompt based on https://github.com/e2b-dev/ai-artifacts
+const CODE_GENERATION_PROMPT = `You are a skilled software engineer. You do not make mistakes. Generate an artifact. You can install additional dependencies. You can use one of the following templates:\n
+
+1. code-interpreter-multilang: "Runs code as a Jupyter notebook cell. Strong data analysis angle. Can use complex visualisation to explain results.". File: script.py. Dependencies installed: python, jupyter, numpy, pandas, matplotlib, seaborn, plotly. Port: none.
+
+2. nextjs-developer: "A Next.js 13+ app that reloads automatically. Using the pages router.". File: pages/index.tsx. Dependencies installed: nextjs@14.2.5, typescript, @types/node, @types/react, @types/react-dom, postcss, tailwindcss, shadcn. Port: 3000.
+
+3. vue-developer: "A Vue.js 3+ app that reloads automatically. Only when asked specifically for a Vue app.". File: app.vue. Dependencies installed: vue@latest, nuxt@3.13.0, tailwindcss. Port: 3000.
+
+4. streamlit-developer: "A streamlit app that reloads automatically.". File: app.py. Dependencies installed: streamlit, pandas, numpy, matplotlib, request, seaborn, plotly. Port: 8501.
+
+5. gradio-developer: "A gradio app. Gradio Blocks/Interface should be called demo.". File: app.py. Dependencies installed: gradio, pandas, numpy, matplotlib, request, seaborn, plotly. Port: 7860.
+
+Provide detail information about the artifact you're about to generate in the following JSON format with the following keys:
+  
+commentary: Describe what you're about to do and the steps you want to take for generating the artifact in great detail.
+template: Name of the template used to generate the artifact.
+title: Short title of the artifact. Max 3 words.
+description: Short description of the artifact. Max 1 sentence.
+additional_dependencies: Additional dependencies required by the artifact. Do not include dependencies that are already included in the template.
+has_additional_dependencies: Detect if additional dependencies that are not included in the template are required by the artifact.
+install_dependencies_command: Command to install additional dependencies required by the artifact.
+port: Port number used by the resulted artifact. Null when no ports are exposed.
+file_path: Relative path to the file, including the file name.
+code: Code generated by the artifact. Only runnable code is allowed.
+
+Make sure to use the correct syntax for the programming language you're using. Make sure to generate only one code file. If you need to use CSS, make sure to include the CSS in the code file using Tailwind CSS syntax.
+`;
+
+// detail information to execute code
+export type CodeArtifact = {
+  commentary: string;
+  template: string;
+  title: string;
+  description: string;
+  additional_dependencies: string[];
+  has_additional_dependencies: boolean;
+  install_dependencies_command: string;
+  port: number | null;
+  file_path: string;
+  code: string;
+  files?: string[];
+};
+
+// Helper function
+async function generateArtifact(
+  query: string,
+  oldCode?: string,
+  attachments?: string[],
+): Promise<CodeArtifact> {
+  const userMessage = `
+  ${query}
+  ${oldCode ? `The existing code is: \n\`\`\`${oldCode}\`\`\`` : ""}
+  ${attachments ? `The attachments are: \n${attachments.join("\n")}` : ""}
+  `;
+  const messages: ChatMessage[] = [
+    { role: "system", content: CODE_GENERATION_PROMPT },
+    { role: "user", content: userMessage },
+  ];
+  try {
+    const response = await Settings.llm.chat({ messages });
+    const content = response.message.content.toString();
+    const jsonContent = content
+      .replace(/^```json\s*|\s*```$/g, "")
+      .replace(/^`+|`+$/g, "")
+      .trim();
+    const artifact = JSON.parse(jsonContent) as CodeArtifact;
+    return artifact;
+  } catch (error) {
+    console.log("Failed to generate artifact", error);
+    throw error;
+  }
+}
+
+export const codeGenerator = tool({
+  name: "artifact",
+  description:
+    "Generate a code artifact based on the input. Don't call this tool if the user has not asked for code generation. E.g. if the user asks to write a description or specification, don't call this tool.",
+  parameters: z.object({
+    requirement: z
+      .string()
+      .describe("The description of the application you want to build."),
+    oldCode: z.string().optional().describe("The existing code to be modified"),
+    sandboxFiles: z
+      .array(z.string())
+      .optional()
+      .describe(
+        "A list of sandbox file paths. Include these files if the code requires them.",
+      ),
+  }),
+  execute: async ({
+    requirement,
+    oldCode,
+    sandboxFiles,
+  }): Promise<JSONValue> => {
+    try {
+      const artifact = await generateArtifact(
+        requirement,
+        oldCode,
+        sandboxFiles, // help the generated code use exact files
+      );
+      if (sandboxFiles) {
+        artifact.files = sandboxFiles;
+      }
+      return artifact as JSONValue;
+    } catch (error) {
+      return { isError: true };
+    }
+  },
+});
diff --git a/packages/tools/src/tools/document-generator.ts b/packages/tools/src/tools/document-generator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a4f74130a2c87f1ed5c6f02c21d2c70d788e547
--- /dev/null
+++ b/packages/tools/src/tools/document-generator.ts
@@ -0,0 +1,142 @@
+import type { BaseTool, ToolMetadata } from "@llamaindex/core/llms";
+import { type JSONSchemaType } from "ajv";
+import { marked } from "marked";
+import path from "node:path";
+import { saveDocument } from "../helper";
+
+const OUTPUT_DIR = "output/tools";
+
+type DocumentParameter = {
+  originalContent: string;
+  fileName: string;
+};
+
+const DEFAULT_METADATA: ToolMetadata<JSONSchemaType<DocumentParameter>> = {
+  name: "document_generator",
+  description:
+    "Generate HTML document from markdown content. Return a file url to the document",
+  parameters: {
+    type: "object",
+    properties: {
+      originalContent: {
+        type: "string",
+        description: "The original markdown content to convert.",
+      },
+      fileName: {
+        type: "string",
+        description: "The name of the document file (without extension).",
+      },
+    },
+    required: ["originalContent", "fileName"],
+  },
+};
+
+const COMMON_STYLES = `
+  body {
+    font-family: Arial, sans-serif;
+    line-height: 1.3;
+    color: #333;
+  }
+  h1, h2, h3, h4, h5, h6 {
+    margin-top: 1em;
+    margin-bottom: 0.5em;
+  }
+  p {
+    margin-bottom: 0.7em;
+  }
+  code {
+    background-color: #f4f4f4;
+    padding: 2px 4px;
+    border-radius: 4px;
+  }
+  pre {
+    background-color: #f4f4f4;
+    padding: 10px;
+    border-radius: 4px;
+    overflow-x: auto;
+  }
+  table {
+    border-collapse: collapse;
+    width: 100%;
+    margin-bottom: 1em;
+  }
+  th, td {
+    border: 1px solid #ddd;
+    padding: 8px;
+    text-align: left;
+  }
+  th {
+    background-color: #f2f2f2;
+    font-weight: bold;
+  }
+  img {
+    max-width: 90%;
+    height: auto;
+    display: block;
+    margin: 1em auto;
+    border-radius: 10px;
+  }
+`;
+
+const HTML_SPECIFIC_STYLES = `
+  body {
+    max-width: 800px;
+    margin: 0 auto;
+    padding: 20px;
+  }
+`;
+
+const HTML_TEMPLATE = `
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        ${COMMON_STYLES}
+        ${HTML_SPECIFIC_STYLES}
+    </style>
+</head>
+<body>
+    {{content}}
+</body>
+</html>
+`;
+
+export interface DocumentGeneratorParams {
+  metadata?: ToolMetadata<JSONSchemaType<DocumentParameter>>;
+}
+
+export class DocumentGenerator implements BaseTool<DocumentParameter> {
+  metadata: ToolMetadata<JSONSchemaType<DocumentParameter>>;
+
+  constructor(params: DocumentGeneratorParams) {
+    this.metadata = params.metadata ?? DEFAULT_METADATA;
+  }
+
+  private static async generateHtmlContent(
+    originalContent: string,
+  ): Promise<string> {
+    return await marked(originalContent);
+  }
+
+  private static generateHtmlDocument(htmlContent: string): string {
+    return HTML_TEMPLATE.replace("{{content}}", htmlContent);
+  }
+
+  async call(input: DocumentParameter): Promise<string> {
+    const { originalContent, fileName } = input;
+
+    const htmlContent =
+      await DocumentGenerator.generateHtmlContent(originalContent);
+    const fileContent = DocumentGenerator.generateHtmlDocument(htmlContent);
+
+    const filePath = path.join(OUTPUT_DIR, `${fileName}.html`);
+
+    return `URL: ${await saveDocument(filePath, fileContent)}`;
+  }
+}
+
+export function getTools(): BaseTool[] {
+  return [new DocumentGenerator({})];
+}
diff --git a/packages/tools/src/tools/duckduckgo.ts b/packages/tools/src/tools/duckduckgo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4f2a3e24a5dae761e65f99da9037bc6285dd8a4b
--- /dev/null
+++ b/packages/tools/src/tools/duckduckgo.ts
@@ -0,0 +1,46 @@
+import { tool } from "@llamaindex/core/tools";
+import { search } from "duck-duck-scrape";
+import { z } from "zod";
+
+export type DuckDuckGoToolOutput = {
+  title: string;
+  description: string;
+  url: string;
+}[];
+
+export const duckduckgo = tool({
+  name: "duckduckgo_search",
+  description:
+    "Use this function to search for information (only text) in the internet using DuckDuckGo.",
+  parameters: z.object({
+    query: z.string().describe("The query to search in DuckDuckGo."),
+    region: z
+      .string()
+      .optional()
+      .describe(
+        "Optional, The region to be used for the search in [country-language] convention, ex us-en, uk-en, ru-ru, etc...",
+      ),
+    maxResults: z
+      .number()
+      .default(10)
+      .optional()
+      .describe(
+        "Optional, The maximum number of results to be returned. Default is 10.",
+      ),
+  }),
+  execute: async ({
+    query,
+    region,
+    maxResults = 10,
+  }): Promise<DuckDuckGoToolOutput> => {
+    const options = region ? { region } : {};
+    const searchResults = await search(query, options);
+    return searchResults.results.slice(0, maxResults).map((result) => {
+      return {
+        title: result.title,
+        description: result.description,
+        url: result.url,
+      };
+    });
+  },
+});
diff --git a/packages/tools/src/tools/form-filling.ts b/packages/tools/src/tools/form-filling.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e1424f1d7833fb966e4c6dfe1eae67752657be8a
--- /dev/null
+++ b/packages/tools/src/tools/form-filling.ts
@@ -0,0 +1,298 @@
+import { Settings } from "@llamaindex/core/global";
+import type { BaseTool, ToolMetadata } from "@llamaindex/core/llms";
+import { type JSONSchemaType } from "ajv";
+import fs from "fs";
+import Papa from "papaparse";
+import path from "path";
+import { saveDocument } from "../helper";
+
+type ExtractMissingCellsParameter = {
+  filePath: string;
+};
+
+export type MissingCell = {
+  rowIndex: number;
+  columnIndex: number;
+  question: string;
+};
+
+const CSV_EXTRACTION_PROMPT = `You are a data analyst. You are given a table with missing cells.
+Your task is to identify the missing cells and the questions needed to fill them.
+IMPORTANT: Column indices should be 0-based
+
+# Instructions:
+- Understand the entire content of the table and the topics of the table.
+- Identify the missing cells and the meaning of the data in the cells.
+- For each missing cell, provide the row index and the correct column index (remember: first data column is 1).
+- For each missing cell, provide the question needed to fill the cell (it's important to provide the question that is relevant to the topic of the table).
+- Since the cell's value should be concise, the question should request a numerical answer or a specific value.
+- Finally, only return the answer in JSON format with the following schema:
+{
+  "missing_cells": [
+    {
+      "rowIndex": number,
+      "columnIndex": number,
+      "question": string
+    }
+  ]
+}
+- If there are no missing cells, return an empty array.
+- The answer is only the JSON object, nothing else and don't wrap it inside markdown code block.
+
+# Example:
+# |    | Name | Age | City |
+# |----|------|-----|------|
+# | 0  | John |     | Paris|
+# | 1  | Mary |     |      |
+# | 2  |      | 30  |      |
+#
+# Your thoughts:
+# - The table is about people's names, ages, and cities.
+# - Row: 1, Column: 2 (Age column), Question: "How old is Mary? Please provide only the numerical answer."
+# - Row: 1, Column: 3 (City column), Question: "In which city does Mary live? Please provide only the city name."
+# Your answer:
+# {
+#   "missing_cells": [
+#     {
+#       "rowIndex": 1,
+#       "columnIndex": 2,
+#       "question": "How old is Mary? Please provide only the numerical answer."
+#     },
+#     {
+#       "rowIndex": 1,
+#       "columnIndex": 3,
+#       "question": "In which city does Mary live? Please provide only the city name."
+#     }
+#   ]
+# }
+
+
+# Here is your task:
+
+- Table content:
+{table_content}
+
+- Your answer:
+`;
+
+const DEFAULT_METADATA: ToolMetadata<
+  JSONSchemaType<ExtractMissingCellsParameter>
+> = {
+  name: "extract_missing_cells",
+  description: `Use this tool to extract missing cells in a CSV file and generate questions to fill them. This tool only works with local file path.`,
+  parameters: {
+    type: "object",
+    properties: {
+      filePath: {
+        type: "string",
+        description: "The local file path to the CSV file.",
+      },
+    },
+    required: ["filePath"],
+  },
+};
+
+export interface ExtractMissingCellsParams {
+  metadata?: ToolMetadata<JSONSchemaType<ExtractMissingCellsParameter>>;
+}
+
+export class ExtractMissingCellsTool
+  implements BaseTool<ExtractMissingCellsParameter>
+{
+  metadata: ToolMetadata<JSONSchemaType<ExtractMissingCellsParameter>>;
+  defaultExtractionPrompt: string;
+
+  constructor(params: ExtractMissingCellsParams) {
+    this.metadata = params.metadata ?? DEFAULT_METADATA;
+    this.defaultExtractionPrompt = CSV_EXTRACTION_PROMPT;
+  }
+
+  private readCsvFile(filePath: string): Promise<string[][]> {
+    return new Promise((resolve, reject) => {
+      fs.readFile(filePath, "utf8", (err, data) => {
+        if (err) {
+          reject(err);
+          return;
+        }
+
+        const parsedData = Papa.parse<string[]>(data, {
+          skipEmptyLines: false,
+        });
+
+        if (parsedData.errors.length) {
+          reject(parsedData.errors);
+          return;
+        }
+
+        // Ensure all rows have the same number of columns as the header
+        const maxColumns = parsedData.data[0]?.length ?? 0;
+        const paddedRows = parsedData.data.map((row) => {
+          return [...row, ...Array(maxColumns - row.length).fill("")];
+        });
+
+        resolve(paddedRows);
+      });
+    });
+  }
+
+  private formatToMarkdownTable(data: string[][]): string {
+    if (data.length === 0) return "";
+
+    const maxColumns = data[0]?.length ?? 0;
+
+    const headerRow = `| ${data[0]?.join(" | ") ?? ""} |`;
+    const separatorRow = `| ${Array(maxColumns).fill("---").join(" | ")} |`;
+
+    const dataRows = data.slice(1).map((row) => {
+      return `| ${row.join(" | ")} |`;
+    });
+
+    return [headerRow, separatorRow, ...dataRows].join("\n");
+  }
+
+  async call(input: ExtractMissingCellsParameter): Promise<MissingCell[]> {
+    const { filePath } = input;
+    let tableContent: string[][];
+    try {
+      tableContent = await this.readCsvFile(filePath);
+    } catch (error) {
+      throw new Error(
+        `Failed to read CSV file. Make sure that you are reading a local file path (not a sandbox path).`,
+      );
+    }
+
+    const prompt = this.defaultExtractionPrompt.replace(
+      "{table_content}",
+      this.formatToMarkdownTable(tableContent),
+    );
+
+    const llm = Settings.llm;
+    const response = await llm.complete({
+      prompt,
+    });
+    const rawAnswer = response.text;
+    const parsedResponse = JSON.parse(rawAnswer) as {
+      missing_cells: MissingCell[];
+    };
+    if (!parsedResponse.missing_cells) {
+      throw new Error(
+        "The answer is not in the correct format. There should be a missing_cells array.",
+      );
+    }
+    const answer = parsedResponse.missing_cells;
+
+    return answer;
+  }
+}
+
+type FillMissingCellsParameter = {
+  filePath: string;
+  cells: {
+    rowIndex: number;
+    columnIndex: number;
+    answer: string;
+  }[];
+};
+
+const FILL_CELLS_METADATA: ToolMetadata<
+  JSONSchemaType<FillMissingCellsParameter>
+> = {
+  name: "fill_missing_cells",
+  description: `Use this tool to fill missing cells in a CSV file with provided answers. This tool only works with local file path.`,
+  parameters: {
+    type: "object",
+    properties: {
+      filePath: {
+        type: "string",
+        description: "The local file path to the CSV file.",
+      },
+      cells: {
+        type: "array",
+        items: {
+          type: "object",
+          properties: {
+            rowIndex: { type: "number" },
+            columnIndex: { type: "number" },
+            answer: { type: "string" },
+          },
+          required: ["rowIndex", "columnIndex", "answer"],
+        },
+        description: "Array of cells to fill with their answers",
+      },
+    },
+    required: ["filePath", "cells"],
+  },
+};
+
+export interface FillMissingCellsParams {
+  metadata?: ToolMetadata<JSONSchemaType<FillMissingCellsParameter>>;
+}
+
+export class FillMissingCellsTool
+  implements BaseTool<FillMissingCellsParameter>
+{
+  metadata: ToolMetadata<JSONSchemaType<FillMissingCellsParameter>>;
+
+  constructor(params: FillMissingCellsParams = {}) {
+    this.metadata = params.metadata ?? FILL_CELLS_METADATA;
+  }
+
+  async call(input: FillMissingCellsParameter): Promise<string> {
+    const { filePath, cells } = input;
+
+    // Read the CSV file
+    const fileContent = await new Promise<string>((resolve, reject) => {
+      fs.readFile(filePath, "utf8", (err, data) => {
+        if (err) {
+          reject(err);
+        } else {
+          resolve(data);
+        }
+      });
+    });
+
+    // Parse CSV with PapaParse
+    const parseResult = Papa.parse<string[]>(fileContent, {
+      header: false, // Ensure the header is not treated as a separate object
+      skipEmptyLines: false, // Ensure empty lines are not skipped
+    });
+
+    if (parseResult.errors.length) {
+      throw new Error(
+        "Failed to parse CSV file: " + parseResult.errors[0]?.message,
+      );
+    }
+
+    const rows = parseResult.data;
+
+    // Fill the cells with answers
+    for (const cell of cells) {
+      // Adjust rowIndex to start from 1 for data rows
+      const adjustedRowIndex = cell.rowIndex + 1;
+      if (
+        adjustedRowIndex < rows.length &&
+        cell.columnIndex < (rows[adjustedRowIndex]?.length ?? 0) &&
+        rows[adjustedRowIndex]
+      ) {
+        rows[adjustedRowIndex][cell.columnIndex] = cell.answer;
+      }
+    }
+
+    // Convert back to CSV format
+    const updatedContent = Papa.unparse(rows, {
+      delimiter: parseResult.meta.delimiter,
+    });
+
+    // Use the helper function to write the file
+    const parsedPath = path.parse(filePath);
+    const newFileName = `${parsedPath.name}-filled${parsedPath.ext}`;
+    const newFilePath = path.join("output/tools", newFileName);
+
+    const newFileUrl = await saveDocument(newFilePath, updatedContent);
+
+    return (
+      "Successfully filled missing cells in the CSV file. File URL to show to the user: " +
+      newFileUrl
+    );
+  }
+}
diff --git a/packages/tools/src/tools/img-gen.ts b/packages/tools/src/tools/img-gen.ts
new file mode 100644
index 0000000000000000000000000000000000000000..654fc6a519d32f4374933c4c16ed49db99d40e48
--- /dev/null
+++ b/packages/tools/src/tools/img-gen.ts
@@ -0,0 +1,93 @@
+import { tool } from "@llamaindex/core/tools";
+import { FormData } from "formdata-node";
+import fs from "fs";
+import got from "got";
+import crypto from "node:crypto";
+import path from "node:path";
+import { Readable } from "stream";
+import { z } from "zod";
+
+export type ImgGeneratorToolOutput = {
+  isSuccess: boolean;
+  imageUrl?: string;
+  errorMessage?: string;
+};
+
+// Constants
+const IMG_OUTPUT_FORMAT = "webp";
+const IMG_OUTPUT_DIR = "output/tools";
+const IMG_GEN_API =
+  "https://api.stability.ai/v2beta/stable-image/generate/core";
+
+// Helper functions
+function checkRequiredEnvVars() {
+  if (!process.env.STABILITY_API_KEY) {
+    throw new Error(
+      "STABILITY_API_KEY key is required to run image generator. Get it here: https://platform.stability.ai/account/keys",
+    );
+  }
+  if (!process.env.FILESERVER_URL_PREFIX) {
+    throw new Error(
+      "FILESERVER_URL_PREFIX is required to display file output after generation",
+    );
+  }
+}
+
+async function promptToImgBuffer(prompt: string): Promise<Buffer> {
+  const form = new FormData();
+  form.append("prompt", prompt);
+  form.append("output_format", IMG_OUTPUT_FORMAT);
+
+  const buffer = await got
+    .post(IMG_GEN_API, {
+      body: form as unknown as Buffer | Readable | string,
+      headers: {
+        Authorization: `Bearer ${process.env.STABILITY_API_KEY}`,
+        Accept: "image/*",
+      },
+    })
+    .buffer();
+
+  return buffer;
+}
+
+function saveImage(buffer: Buffer): string {
+  const filename = `${crypto.randomUUID()}.${IMG_OUTPUT_FORMAT}`;
+
+  // Create output directory if it doesn't exist
+  if (!fs.existsSync(IMG_OUTPUT_DIR)) {
+    fs.mkdirSync(IMG_OUTPUT_DIR, { recursive: true });
+  }
+
+  const outputPath = path.join(IMG_OUTPUT_DIR, filename);
+  fs.writeFileSync(outputPath, buffer);
+
+  const url = `${process.env.FILESERVER_URL_PREFIX}/${IMG_OUTPUT_DIR}/${filename}`;
+  console.log(`Saved image to ${outputPath}.\nURL: ${url}`);
+
+  return url;
+}
+
+export const imageGenerator = tool({
+  name: "image_generator",
+  description: "Use this function to generate an image based on the prompt.",
+  parameters: z.object({
+    prompt: z.string().describe("The prompt to generate the image"),
+  }),
+  execute: async ({ prompt }): Promise<ImgGeneratorToolOutput> => {
+    // Check required environment variables
+    checkRequiredEnvVars();
+
+    try {
+      const buffer = await promptToImgBuffer(prompt);
+      const imageUrl = saveImage(buffer);
+      return { isSuccess: true, imageUrl };
+    } catch (error) {
+      console.error(error);
+      return {
+        isSuccess: false,
+        errorMessage: "Failed to generate image. Please try again.",
+      };
+    }
+  },
+});
diff --git a/packages/tools/src/tools/interpreter.ts b/packages/tools/src/tools/interpreter.ts
index bcc4019cb1a9bcce2fa64e2b7946c45314921926..20db2df49c7dfdfe3b2d0b0a2cc7716bf49b8e69 100644
--- a/packages/tools/src/tools/interpreter.ts
+++ b/packages/tools/src/tools/interpreter.ts
@@ -1,4 +1,5 @@
 import { type Logs, Result, Sandbox } from "@e2b/code-interpreter";
+import type { JSONValue } from "@llamaindex/core/global";
 import { type BaseTool, type ToolMetadata } from "@llamaindex/core/llms";
 import type { JSONSchemaType } from "ajv";
 import fs from "fs";
@@ -164,9 +165,9 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
     return result;
   }
 
-  async call(input: InterpreterParameter): Promise<InterpreterToolOutput> {
+  async call(input: InterpreterParameter) {
     const result = await this.codeInterpret(input);
-    return result;
+    return result as JSONValue;
   }
 
   async close() {
diff --git a/packages/tools/src/tools/openapi-action.ts b/packages/tools/src/tools/openapi-action.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38dfbf317faae356bdd514cc182c5080bd67bfc0
--- /dev/null
+++ b/packages/tools/src/tools/openapi-action.ts
@@ -0,0 +1,168 @@
+import SwaggerParser from "@apidevtools/swagger-parser";
+import type { JSONValue } from "@llamaindex/core/global";
+import type { ToolMetadata } from "@llamaindex/core/llms";
+import { FunctionTool } from "@llamaindex/core/tools";
+import { type JSONSchemaType } from "ajv";
+import got from "got";
+
+interface DomainHeaders {
+  [key: string]: { [header: string]: string };
+}
+
+type Input = {
+  url: string;
+  params: object;
+};
+
+type APIInfo = {
+  description: string;
+  title: string;
+};
+
+export class OpenAPIActionTool {
+  // cache the loaded specs by URL
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  private static specs: Record<string, any> = {};
+
+  private readonly INVALID_URL_PROMPT =
+    "This url did not include a hostname or scheme. Please determine the complete URL and try again.";
+
+  private createLoadSpecMetaData = (info: APIInfo) => {
+    return {
+      name: "load_openapi_spec",
+      description: `Use this to retrieve the OpenAPI spec for the API named ${info.title} with the following description: ${info.description}. Call it before making any requests to the API.`,
+    };
+  };
+
+  private readonly createMethodCallMetaData = (
+    method: "POST" | "PATCH" | "GET",
+    info: APIInfo,
+  ) => {
+    return {
+      name: `${method.toLowerCase()}_request`,
+      description: `Use this to call the ${method} method on the API named ${info.title}`,
+      parameters: {
+        type: "object",
+        properties: {
+          url: {
+            type: "string",
+            description: `The url to make the ${method} request against`,
+          },
+          params: {
+            type: "object",
+            description:
+              method === "GET"
+                ? "the URL parameters to provide with the get request"
+                : `the key-value pairs to provide with the ${method} request`,
+          },
+        },
+        required: ["url"],
+      },
+    } as ToolMetadata<JSONSchemaType<Input>>;
+  };
+
+  constructor(
+    public openapi_uri: string,
+    public domainHeaders: DomainHeaders = {},
+  ) {}
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  async loadOpenapiSpec(url: string): Promise<any> {
+    const api = await SwaggerParser.validate(url);
+    return {
+      servers: "servers" in api ? api.servers : "",
+      info: { description: api.info.description, title: api.info.title },
+      endpoints: api.paths,
+    };
+  }
+
+  async getRequest(input: Input): Promise<JSONValue> {
+    if (!this.validUrl(input.url)) {
+      return this.INVALID_URL_PROMPT;
+    }
+    try {
+      const data = await got
+        .get(input.url, {
+          headers: this.getHeadersForUrl(input.url),
+          searchParams: input.params as URLSearchParams,
+        })
+        .json();
+      return data as JSONValue;
+    } catch (error) {
+      return error as JSONValue;
+    }
+  }
+
+  async postRequest(input: Input): Promise<JSONValue> {
+    if (!this.validUrl(input.url)) {
+      return this.INVALID_URL_PROMPT;
+    }
+    try {
+      const res = await got.post(input.url, {
+        headers: this.getHeadersForUrl(input.url),
+        json: input.params,
+      });
+      return res.body as JSONValue;
+    } catch (error) {
+      return error as JSONValue;
+    }
+  }
+
+  async patchRequest(input: Input): Promise<JSONValue> {
+    if (!this.validUrl(input.url)) {
+      return this.INVALID_URL_PROMPT;
+    }
+    try {
+      const res = await got.patch(input.url, {
+        headers: this.getHeadersForUrl(input.url),
+        json: input.params,
+      });
+      return res.body as JSONValue;
+    } catch (error) {
+      return error as JSONValue;
+    }
+  }
+
+  public async toToolFunctions() {
+    if (!OpenAPIActionTool.specs[this.openapi_uri]) {
+      console.log(`Loading spec for URL: ${this.openapi_uri}`);
+      const spec = await this.loadOpenapiSpec(this.openapi_uri);
+      OpenAPIActionTool.specs[this.openapi_uri] = spec;
+    }
+    const spec = OpenAPIActionTool.specs[this.openapi_uri];
+    // TODO: read endpoints with parameters from spec and create one tool for each endpoint
+    // For now, we just create a tool for each HTTP method which does not work well for passing parameters
+    return [
+      FunctionTool.from(() => {
+        return spec;
+      }, this.createLoadSpecMetaData(spec.info)),
+      FunctionTool.from(
+        this.getRequest.bind(this),
+        this.createMethodCallMetaData("GET", spec.info),
+      ),
+      FunctionTool.from(
+        this.postRequest.bind(this),
+        this.createMethodCallMetaData("POST", spec.info),
+      ),
+      FunctionTool.from(
+        this.patchRequest.bind(this),
+        this.createMethodCallMetaData("PATCH", spec.info),
+      ),
+    ];
+  }
+
+  private validUrl(url: string): boolean {
+    const parsed = new URL(url);
+    return !!parsed.protocol && !!parsed.hostname;
+  }
+
+  private getDomain(url: string): string {
+    const parsed = new URL(url);
+    return parsed.hostname;
+  }
+
+  private getHeadersForUrl(url: string): { [header: string]: string } {
+    const domain = this.getDomain(url);
+    return this.domainHeaders[domain] || {};
+  }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c61deeb85ff7ee22dab2534f943c8ed1a8e73fa..ebadb90e7545187efb2a6ce3e9d29a60e8565c29 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1733,6 +1733,9 @@ importers:
 
   packages/tools:
     dependencies:
+      '@apidevtools/swagger-parser':
+        specifier: ^10.1.0
+        version: 10.1.1(openapi-types@12.1.3)
       '@e2b/code-interpreter':
         specifier: ^1.0.4
         version: 1.0.4
@@ -1745,6 +1748,21 @@ importers:
       ajv:
         specifier: ^8.12.0
         version: 8.17.1
+      duck-duck-scrape:
+        specifier: ^2.2.5
+        version: 2.2.7
+      formdata-node:
+        specifier: ^6.0.3
+        version: 6.0.3
+      got:
+        specifier: ^14.4.1
+        version: 14.4.6
+      marked:
+        specifier: ^14.1.2
+        version: 14.1.4
+      papaparse:
+        specifier: ^5.4.1
+        version: 5.5.2
       wikipedia:
         specifier: ^2.1.2
         version: 2.1.2
@@ -1755,6 +1773,9 @@ importers:
       '@types/node':
         specifier: ^22.9.0
         version: 22.9.0
+      '@types/papaparse':
+        specifier: ^5.3.15
+        version: 5.3.15
       bunchee:
         specifier: 6.4.0
         version: 6.4.0(typescript@5.7.3)
@@ -1981,10 +2002,26 @@ packages:
   '@anthropic-ai/sdk@0.37.0':
     resolution: {integrity: sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==}
 
+  '@apidevtools/json-schema-ref-parser@11.7.2':
+    resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
+    engines: {node: '>= 16'}
+
   '@apidevtools/json-schema-ref-parser@11.9.3':
     resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==}
     engines: {node: '>= 16'}
 
+  '@apidevtools/openapi-schemas@2.1.0':
+    resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
+    engines: {node: '>=10'}
+
+  '@apidevtools/swagger-methods@3.0.2':
+    resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
+
+  '@apidevtools/swagger-parser@10.1.1':
+    resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==}
+    peerDependencies:
+      openapi-types: '>=7'
+
   '@assemblyscript/loader@0.27.34':
     resolution: {integrity: sha512-aUc12nBRN04dHn439RiI0G9X8CeOzbVuKspiVL1O8yS82neU3zTYjVFi9mz2+ZpSD2Laa7H0sUJ91jfhzQERMw==}
 
@@ -4824,6 +4861,10 @@ packages:
     resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
     engines: {node: '>=14.16'}
 
+  '@sindresorhus/is@7.0.1':
+    resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==}
+    engines: {node: '>=18'}
+
   '@sitespeed.io/tracium@0.3.3':
     resolution: {integrity: sha512-dNZafjM93Y+F+sfwTO5gTpsGXlnc/0Q+c2+62ViqP3gkMWvHEMSKkaEHgVJLcLg3i/g19GSIPziiKpgyne07Bw==}
     engines: {node: '>=8'}
@@ -5468,6 +5509,9 @@ packages:
   '@types/node@22.9.0':
     resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
 
+  '@types/papaparse@5.3.15':
+    resolution: {integrity: sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==}
+
   '@types/pg@8.11.11':
     resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==}
 
@@ -6320,6 +6364,10 @@ packages:
     resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
     engines: {node: '>=14.16'}
 
+  cacheable-request@12.0.1:
+    resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==}
+    engines: {node: '>=18'}
+
   call-bind-apply-helpers@1.0.1:
     resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==}
     engines: {node: '>= 0.4'}
@@ -6332,6 +6380,9 @@ packages:
     resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
     engines: {node: '>= 0.4'}
 
+  call-me-maybe@1.0.2:
+    resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==}
+
   callguard@2.0.0:
     resolution: {integrity: sha512-I3nd+fuj20FK1qu00ImrbH+II+8ULS6ioYr9igqR1xyqySoqc3DiHEyUM0mkoAdKeLGg2CtGnO8R3VRQX5krpQ==}
 
@@ -6911,6 +6962,9 @@ packages:
     resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
     engines: {node: '>=12'}
 
+  duck-duck-scrape@2.2.7:
+    resolution: {integrity: sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ==}
+
   duck@0.1.12:
     resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==}
 
@@ -7793,6 +7847,10 @@ packages:
     resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
     engines: {node: '>=16'}
 
+  got@14.4.6:
+    resolution: {integrity: sha512-rnhwfM/PhMNJ1i17k3DuDqgj0cKx3IHxBKVv/WX1uDKqrhi2Gv3l7rhPThR/Cc6uU++dD97W9c8Y0qyw9x0jag==}
+    engines: {node: '>=20'}
+
   gpt-tokenizer@2.8.1:
     resolution: {integrity: sha512-8+a9ojzqfgiF3TK4oivGYjlycD8g5igLt8NQw3ndOIgLVKSGJDhUDNAfYSbtyyuTkha3R/R9F8XrwC7/B5TKfQ==}
 
@@ -8796,6 +8854,11 @@ packages:
   markdown-table@3.0.4:
     resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
 
+  marked@14.1.4:
+    resolution: {integrity: sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==}
+    engines: {node: '>= 18'}
+    hasBin: true
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
@@ -9400,6 +9463,11 @@ packages:
     resolution: {integrity: sha512-VVw8O5KrfvwqAFeNZEgBbdgA+AQaBlHcXEootWU7TWDaFWFI0VLfzyKMsRjnfdS3cVCpWmI04xLJonCvEv11VQ==}
     engines: {node: '>=0.4.10'}
 
+  needle@3.3.1:
+    resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
+    engines: {node: '>= 4.4.x'}
+    hasBin: true
+
   negotiator@1.0.0:
     resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
     engines: {node: '>= 0.6'}
@@ -9686,6 +9754,9 @@ packages:
   openapi-sampler@1.6.1:
     resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==}
 
+  openapi-types@12.1.3:
+    resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
+
   openapi-typescript-helpers@0.0.8:
     resolution: {integrity: sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g==}
 
@@ -9733,6 +9804,10 @@ packages:
     resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
     engines: {node: '>=12.20'}
 
+  p-cancelable@4.0.1:
+    resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==}
+    engines: {node: '>=14.16'}
+
   p-filter@2.1.0:
     resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==}
     engines: {node: '>=8'}
@@ -12317,12 +12392,33 @@ snapshots:
     transitivePeerDependencies:
       - encoding
 
+  '@apidevtools/json-schema-ref-parser@11.7.2':
+    dependencies:
+      '@jsdevtools/ono': 7.1.3
+      '@types/json-schema': 7.0.15
+      js-yaml: 4.1.0
+
   '@apidevtools/json-schema-ref-parser@11.9.3':
     dependencies:
       '@jsdevtools/ono': 7.1.3
       '@types/json-schema': 7.0.15
       js-yaml: 4.1.0
 
+  '@apidevtools/openapi-schemas@2.1.0': {}
+
+  '@apidevtools/swagger-methods@3.0.2': {}
+
+  '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)':
+    dependencies:
+      '@apidevtools/json-schema-ref-parser': 11.7.2
+      '@apidevtools/openapi-schemas': 2.1.0
+      '@apidevtools/swagger-methods': 3.0.2
+      '@jsdevtools/ono': 7.1.3
+      ajv: 8.17.1
+      ajv-draft-04: 1.0.0(ajv@8.17.1)
+      call-me-maybe: 1.0.2
+      openapi-types: 12.1.3
+
   '@assemblyscript/loader@0.27.34': {}
 
   '@aws-crypto/crc32@3.0.0':
@@ -15731,6 +15827,8 @@ snapshots:
 
   '@sindresorhus/is@5.6.0': {}
 
+  '@sindresorhus/is@7.0.1': {}
+
   '@sitespeed.io/tracium@0.3.3':
     dependencies:
       debug: 4.4.0
@@ -16476,6 +16574,10 @@ snapshots:
     dependencies:
       undici-types: 6.19.8
 
+  '@types/papaparse@5.3.15':
+    dependencies:
+      '@types/node': 22.9.0
+
   '@types/pg@8.11.11':
     dependencies:
       '@types/node': 22.9.0
@@ -17565,6 +17667,16 @@ snapshots:
       normalize-url: 8.0.1
       responselike: 3.0.0
 
+  cacheable-request@12.0.1:
+    dependencies:
+      '@types/http-cache-semantics': 4.0.4
+      get-stream: 9.0.1
+      http-cache-semantics: 4.1.1
+      keyv: 4.5.4
+      mimic-response: 4.0.0
+      normalize-url: 8.0.1
+      responselike: 3.0.0
+
   call-bind-apply-helpers@1.0.1:
     dependencies:
       es-errors: 1.3.0
@@ -17582,6 +17694,8 @@ snapshots:
       call-bind-apply-helpers: 1.0.1
       get-intrinsic: 1.2.7
 
+  call-me-maybe@1.0.2: {}
+
   callguard@2.0.0: {}
 
   callsites@3.1.0: {}
@@ -18129,6 +18243,11 @@ snapshots:
 
   dotenv@16.4.7: {}
 
+  duck-duck-scrape@2.2.7:
+    dependencies:
+      html-entities: 2.5.2
+      needle: 3.3.1
+
   duck@0.1.12:
     dependencies:
       underscore: 1.13.7
@@ -19588,6 +19707,20 @@ snapshots:
       p-cancelable: 3.0.0
       responselike: 3.0.0
 
+  got@14.4.6:
+    dependencies:
+      '@sindresorhus/is': 7.0.1
+      '@szmarczak/http-timer': 5.0.1
+      cacheable-lookup: 7.0.0
+      cacheable-request: 12.0.1
+      decompress-response: 6.0.0
+      form-data-encoder: 4.0.2
+      http2-wrapper: 2.2.1
+      lowercase-keys: 3.0.0
+      p-cancelable: 4.0.1
+      responselike: 3.0.0
+      type-fest: 4.34.1
+
   gpt-tokenizer@2.8.1: {}
 
   graceful-fs@4.2.11: {}
@@ -20749,6 +20882,8 @@ snapshots:
 
   markdown-table@3.0.4: {}
 
+  marked@14.1.4: {}
+
   math-intrinsics@1.1.0: {}
 
   md-utils-ts@2.0.0: {}
@@ -21838,6 +21973,11 @@ snapshots:
       - socks
       - supports-color
 
+  needle@3.3.1:
+    dependencies:
+      iconv-lite: 0.6.3
+      sax: 1.4.1
+
   negotiator@1.0.0: {}
 
   neo-async@2.6.2: {}
@@ -22162,6 +22302,8 @@ snapshots:
       fast-xml-parser: 4.5.3
       json-pointer: 0.6.2
 
+  openapi-types@12.1.3: {}
+
   openapi-typescript-helpers@0.0.8: {}
 
   opener@1.5.2: {}
@@ -22235,6 +22377,8 @@ snapshots:
 
   p-cancelable@3.0.0: {}
 
+  p-cancelable@4.0.1: {}
+
   p-filter@2.1.0:
     dependencies:
       p-map: 2.1.0