diff --git a/.github/workflows/autogpt-infra-ci.yml b/.github/workflows/autogpt-infra-ci.yml new file mode 100644 index 000000000000..2605cd02de05 --- /dev/null +++ b/.github/workflows/autogpt-infra-ci.yml @@ -0,0 +1,33 @@ +name: AutoGPT Builder Infra + +on: + push: + branches: [ master ] + paths: + - '.github/workflows/autogpt-infra-ci.yml' + - 'rnd/infra/**' + pull_request: + paths: + - '.github/workflows/autogpt-infra-ci.yml' + - 'rnd/infra/**' + +defaults: + run: + shell: bash + working-directory: rnd/infra + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: TFLint + uses: pauloconnor/tflint-action@v0.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tflint_path: terraform/ + tflint_recurse: true + tflint_changed_only: false diff --git a/.github/workflows/autogpt-server-ci.yml b/.github/workflows/autogpt-server-ci.yml index 96cc2d430f58..a996ccc0c4c1 100644 --- a/.github/workflows/autogpt-server-ci.yml +++ b/.github/workflows/autogpt-server-ci.yml @@ -31,10 +31,12 @@ jobs: matrix: python-version: ["3.10"] platform-os: [ubuntu, macos, macos-arm64, windows] + db-platform: [postgres, sqlite] runs-on: ${{ matrix.platform-os != 'macos-arm64' && format('{0}-latest', matrix.platform-os) || 'macos-14' }} steps: - name: Setup PostgreSQL + if: matrix.db-platform == 'postgres' uses: ikalnytskyi/action-setup-postgres@v6 with: username: ${{ secrets.DB_USER }} @@ -114,14 +116,24 @@ jobs: - name: Install Python dependencies run: poetry install - - name: Generate Prisma Client + - name: Generate Prisma Client (Postgres) + if: matrix.db-platform == 'postgres' run: poetry run prisma generate --schema postgres/schema.prisma - - name: Run Database Migrations + - name: Run Database Migrations (Postgres) + if: matrix.db-platform == 'postgres' run: poetry run prisma migrate dev --schema postgres/schema.prisma --name updates env: CONNECTION_STR: ${{ steps.postgres.outputs.connection-uri }} + - name: Generate Prisma Client (SQLite) + if: matrix.db-platform == 'sqlite' + run: poetry run prisma generate + + - name: Run Database Migrations (SQLite) + if: matrix.db-platform == 'sqlite' + run: poetry run prisma migrate dev --name updates + - name: Run Linter run: poetry run lint diff --git a/.vscode/all-projects.code-workspace b/.vscode/all-projects.code-workspace index 6f355340f516..3b29dd69ffb1 100644 --- a/.vscode/all-projects.code-workspace +++ b/.vscode/all-projects.code-workspace @@ -33,7 +33,9 @@ "path": ".." } ], - "settings": {}, + "settings": { + "python.analysis.typeCheckingMode": "basic", + }, "extensions": { "recommendations": [ "charliermarsh.ruff", diff --git a/autogpt/.env.template b/autogpt/.env.template index 8d4894988c53..80fa39263a6a 100644 --- a/autogpt/.env.template +++ b/autogpt/.env.template @@ -105,6 +105,7 @@ ## HUGGINGFACE_API_TOKEN - HuggingFace API token (Default: None) # HUGGINGFACE_API_TOKEN= + ### Stable Diffusion (IMAGE_PROVIDER=sdwebui) ## SD_WEBUI_AUTH - Stable Diffusion Web UI username:password pair (Default: None) diff --git a/autogpt/.vscode/settings.json b/autogpt/.vscode/settings.json new file mode 100644 index 000000000000..31f7f02be464 --- /dev/null +++ b/autogpt/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic", +} diff --git a/benchmark/.vscode/settings.json b/benchmark/.vscode/settings.json index 3445835beddf..2e6891d2b872 100644 --- a/benchmark/.vscode/settings.json +++ b/benchmark/.vscode/settings.json @@ -2,5 +2,5 @@ "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, - "python.formatting.provider": "none" + "python.analysis.typeCheckingMode": "basic", } diff --git a/docs/content/forge/components/built-in-components.md b/docs/content/forge/components/built-in-components.md index 77ae76909a79..39f607a21212 100644 --- a/docs/content/forge/components/built-in-components.md +++ b/docs/content/forge/components/built-in-components.md @@ -155,11 +155,12 @@ Allows agent to search the web. Google credentials aren't required for DuckDuckG ### `WebSearchConfiguration` -| Config variable | Details | Type | Default | -| -------------------------------- | ----------------------------------------------------------------------- | ----- | ------- | -| `google_api_key` | Google API key, *ENV:* `GOOGLE_API_KEY` | `str` | `None` | -| `google_custom_search_engine_id` | Google Custom Search Engine ID, *ENV:* `GOOGLE_CUSTOM_SEARCH_ENGINE_ID` | `str` | `None` | -| `duckduckgo_max_attempts` | Maximum number of attempts to search using DuckDuckGo | `int` | `3` | +| Config variable | Details | Type | Default | +| -------------------------------- | ----------------------------------------------------------------------- | --------------------------- | ------- | +| `google_api_key` | Google API key, *ENV:* `GOOGLE_API_KEY` | `str` | `None` | +| `google_custom_search_engine_id` | Google Custom Search Engine ID, *ENV:* `GOOGLE_CUSTOM_SEARCH_ENGINE_ID` | `str` | `None` | +| `duckduckgo_max_attempts` | Maximum number of attempts to search using DuckDuckGo | `int` | `3` | +| `duckduckgo_backend` | Backend to be used for DDG sdk | `"api" \| "html" \| "lite"` | `"api"` | ### DirectiveProvider @@ -183,6 +184,7 @@ Allows agent to read websites using Selenium. | `headless` | Run browser in headless mode | `bool` | `True` | | `user_agent` | User agent used by the browser | `str` | `"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"` | | `browse_spacy_language_model` | Spacy language model used for chunking text | `str` | `"en_core_web_sm"` | +| `selenium_proxy` | Http proxy to use with Selenium | `str` | `None` | ### DirectiveProvider diff --git a/docs/content/server/advanced_setup.md b/docs/content/server/advanced_setup.md new file mode 100644 index 000000000000..5bba2ce47b26 --- /dev/null +++ b/docs/content/server/advanced_setup.md @@ -0,0 +1,41 @@ +# Advanced Setup + +The advanced steps below are intended for people with sysadmin experience. If you are not comfortable with these steps, please refer to the [basic setup guide](setup.md). + +## Introduction + +For the advanced setup, first follow the [basic setup guide](setup.md) to get the server up and running. Once you have the server running, you can follow the steps below to configure the server for your specific needs. + +## Configuration + +### Setting config via environment variables + +The server uses environment variables to store configs. You can set these environment variables in a `.env` file in the root of the project. The `.env` file should look like this: + +```bash +# .env +KEY1=value1 +KEY2=value2 +``` + +The server will automatically load the `.env` file when it starts. You can also set the environment variables directly in your shell. Refer to your operating system's documentation on how to set environment variables in the current session. + +The valid options are listed in `.env.example` in the root of the builder and server directories. You can copy the `.env.example` file to `.env` and modify the values as needed. + +```bash +# Copy the .env.example file to .env +cp .env.example .env +``` + +### Secrets directory + +The secret directory is located at `./secrets`. You can store any secrets you need in this directory. The server will automatically load the secrets when it starts. + +An example for a secret called `my_secret` would look like this: + +```bash +# ./secrets/my_secret +my_secret_value +``` + +This is useful when running on docker so you can copy the secrets into the container without exposing them in the Dockerfile. diff --git a/docs/content/server/setup.md b/docs/content/server/setup.md new file mode 100644 index 000000000000..1bec7e2800a3 --- /dev/null +++ b/docs/content/server/setup.md @@ -0,0 +1,102 @@ +# Setting up the server + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) + +## Introduction + +This guide will help you setup the server and builder for the project. + + + +We also offer this in video format. You can check it out [here](https://github.com/Significant-Gravitas/AutoGPT#how-to-get-started) + +!!! warning + **DO NOT FOLLOW ANY OUTSIDE TUTORIALS AS THEY WILL LIKELY BE OUT OF DATE** + +## Prerequisites + +To setup the server, you need to have the following installed: + +- [Node.js](https://nodejs.org/en/) +- [Python 3.10](https://www.python.org/downloads/) + +### Checking if you have Node.js and Python installed + +You can check if you have Node.js installed by running the following command: + +```bash +node -v +``` + +You can check if you have Python installed by running the following command: + +```bash +python --version +``` + +Once you have node and python installed, you can proceed to the next step. + +### Installing the package managers + +In order to install the dependencies, you need to have the appropriate package managers installed. + +- Installing Yarn + +Yarn is a package manager for Node.js. You can install it by running the following command: + +```bash +npm install -g yarn +``` + +- Installing Poetry + +Poetry is a package manager for Python. You can install it by running the following command: + +```bash +pip install poetry +``` + +### Installing the dependencies + +Once you have installed Yarn and Poetry, you can run the following command to install the dependencies: + +```bash +cd rnd/autogpt_server +poetry install +``` + +**In another terminal**, run the following command to install the dependencies for the frontend: + +```bash +cd rnd/autogpt_builder +yarn install +``` + +Once you have installed the dependencies, you can proceed to the next step. + +### Setting up the database + +In order to setup the database, you need to run the following command, in the same terminal you ran the `poetry install` command: + +```bash +poetry run prisma migrate deploy +``` + +### Running the server + +To run the server, you can run the following command in the same terminal you ran the `poetry install` command: + +```bash +poetry run app +``` + +In the other terminal, you can run the following command to start the frontend: + +```bash +yarn dev +``` + +### Checking if the server is running + +You can check if the server is running by visiting [http://localhost:3000](http://localhost:3000) in your browser. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b143d08f7fb8..58f7de04202e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -7,6 +7,8 @@ nav: - The AutoGPT Server 🆕: - Build your own Blocks: server/new_blocks.md + - Setup: server/setup.md + - Advanced Setup: server/advanced_setup.md - AutoGPT Agent: - Introduction: AutoGPT/index.md diff --git a/forge/.vscode/settings.json b/forge/.vscode/settings.json new file mode 100644 index 000000000000..31f7f02be464 --- /dev/null +++ b/forge/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic", +} diff --git a/forge/forge/components/web/search.py b/forge/forge/components/web/search.py index a381fd950a32..2cc2b4b0b362 100644 --- a/forge/forge/components/web/search.py +++ b/forge/forge/components/web/search.py @@ -1,7 +1,7 @@ import json import logging import time -from typing import Iterator, Optional +from typing import Iterator, Literal, Optional from duckduckgo_search import DDGS from pydantic import BaseModel, SecretStr @@ -24,6 +24,7 @@ class WebSearchConfiguration(BaseModel): None, from_env="GOOGLE_CUSTOM_SEARCH_ENGINE_ID", exclude=True ) duckduckgo_max_attempts: int = 3 + duckduckgo_backend: Literal["api", "html", "lite"] = "api" class WebSearchComponent( @@ -89,7 +90,9 @@ def web_search(self, query: str, num_results: int = 8) -> str: if not query: return json.dumps(search_results) - search_results = DDGS().text(query, max_results=num_results) + search_results = DDGS().text( + query, max_results=num_results, backend=self.config.duckduckgo_backend + ) if search_results: break diff --git a/forge/forge/components/web/selenium.py b/forge/forge/components/web/selenium.py index 69f5427e0087..62042cce5fc8 100644 --- a/forge/forge/components/web/selenium.py +++ b/forge/forge/components/web/selenium.py @@ -68,6 +68,8 @@ class WebSeleniumConfiguration(BaseModel): """User agent used by the browser""" browse_spacy_language_model: str = "en_core_web_sm" """Spacy language model used for chunking text""" + selenium_proxy: Optional[str] = None + """Http proxy to use with Selenium""" class WebSeleniumComponent( @@ -301,6 +303,9 @@ async def open_page_in_browser(self, url: str) -> WebDriver: options.add_argument("--headless=new") options.add_argument("--disable-gpu") + if self.config.selenium_proxy: + options.add_argument(f"--proxy-server={self.config.selenium_proxy}") + self._sideload_chrome_extensions(options, self.data_dir / "assets" / "crx") if (chromium_driver_path := Path("/usr/bin/chromedriver")).exists(): diff --git a/forge/tutorials/001_getting_started.md b/forge/tutorials/001_getting_started.md index 56cd70a71a05..00268c7315a3 100644 --- a/forge/tutorials/001_getting_started.md +++ b/forge/tutorials/001_getting_started.md @@ -1,6 +1,6 @@ ## [AutoGPT Forge Part 1: A Comprehensive Guide to Your First Steps](https://aiedge.medium.com/autogpt-forge-a-comprehensive-guide-to-your-first-steps-a1dfdf46e3b4) -![Header](../../../docs/content/imgs/quickstart/000_header_img.png) +![Header](..%2F..%2F..%2Fdocs/content/imgs/quickstart/000_header_img.png) **Written by Craig Swift & [Ryan Brandt](https://github.com/paperMoose)** @@ -15,22 +15,22 @@ The Forge serves as a comprehensive template for building your own AutoGPT agent To begin, you need to fork the [repository](https://github.com/Significant-Gravitas/AutoGPT) by navigating to the main page of the repository and clicking **Fork** in the top-right corner. -![The Github repository](../../../docs/content/imgs/quickstart/001_repo.png) +![The Github repository](..%2F..%2F..%2Fdocs/content/imgs/quickstart/001_repo.png) Follow the on-screen instructions to complete the process. -![Create Fork Page](../../../docs/content/imgs/quickstart/002_fork.png) +![Create Fork Page](..%2F..%2F..%2Fdocs/content/imgs/quickstart/002_fork.png) ### Cloning the Repository Next, clone your newly forked repository to your local system. Ensure you have Git installed to proceed with this step. You can download Git from [here](https://git-scm.com/downloads). Then clone the repo using the following command and the url for your repo. You can find the correct url by clicking on the green Code button on your repos main page. -![img_1.png](../../../docs/content/imgs/quickstart/003A_clone.png) +![img_1.png](..%2F..%2F..%2Fdocs/content/imgs/quickstart/003A_clone.png) ```bash # replace the url with the one for your forked repo git clone https://github.com/ ``` -![Clone the Repository](../../../docs/content/imgs/quickstart/003_clone.png) +![Clone the Repository](..%2F..%2F..%2Fdocs/content/imgs/quickstart/003_clone.png) ### Setting up the Project @@ -41,8 +41,8 @@ cd AutoGPT ``` To set up the project, utilize the `./run setup` command in the terminal. Follow the instructions to install necessary dependencies and set up your GitHub access token. -![Setup the Project](../../../docs/content/imgs/quickstart/005_setup.png) -![Setup Complete](../../../docs/content/imgs/quickstart/006_setup_complete.png) +![Setup the Project](..%2F..%2F..%2Fdocs/content/imgs/quickstart/005_setup.png) +![Setup Complete](..%2F..%2F..%2Fdocs/content/imgs/quickstart/006_setup_complete.png) ## Section 3: Creating Your Agent @@ -55,7 +55,7 @@ Create your agent template using the command: ``` Replacing YOUR_AGENT_NAME with the name you chose in the previous step. -![Create an Agent](../../../docs/content/imgs/quickstart/007_create_agent.png) +![Create an Agent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/007_create_agent.png) ## Section 4: Running Your Agent @@ -66,13 +66,13 @@ Begin by starting your agent using the command: ``` This will initiate the agent on `http://localhost:8000/`. -![Start the Agent](../../../docs/content/imgs/quickstart/009_start_agent.png) +![Start the Agent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/009_start_agent.png) ### Logging in and Sending Tasks to Your Agent Access the frontend at `http://localhost:8000/` and log in using a Google or GitHub account. Once you're logged you'll see the agent tasking interface! However... the agent won't do anything yet. We'll implement the logic for our agent to run tasks in the upcoming tutorial chapters. -![Login](../../../docs/content/imgs/quickstart/010_login.png) -![Home](../../../docs/content/imgs/quickstart/011_home.png) +![Login](..%2F..%2F..%2Fdocs/content/imgs/quickstart/010_login.png) +![Home](..%2F..%2F..%2Fdocs/content/imgs/quickstart/011_home.png) ### Stopping and Restarting Your Agent When needed, use Ctrl+C to end the session or use the stop command: diff --git a/forge/tutorials/002_blueprint_of_an_agent.md b/forge/tutorials/002_blueprint_of_an_agent.md index 15e192651a04..8bacf3a3b941 100644 --- a/forge/tutorials/002_blueprint_of_an_agent.md +++ b/forge/tutorials/002_blueprint_of_an_agent.md @@ -7,7 +7,7 @@ --- -![Header](../../../docs/content/imgs/quickstart/t2_01.png) +![Header](..%2F..%2Fdocs/content/imgs/quickstart/t2_01.png) @@ -21,14 +21,14 @@ Large Language Models (LLMs) are state-of-the-art machine learning models that h Traditional autonomous agents operated with limited knowledge, often confined to specific tasks or environments. They were like calculators — efficient but limited to predefined functions. LLM-based agents, on the other hand don’t just compute; they understand, reason, and then act, drawing from a vast reservoir of information. -![AI visualising AI researchers hard at work](../../../docs/content/imgs/quickstart/t2_02.png) +![AI visualising AI researchers hard at work](..%2F..%2Fdocs/content/imgs/quickstart/t2_02.png) ## The Anatomy of an LLM-Based AI Agent Diving deep into the core of an LLM-based AI agent, we find it’s structured much like a human, with distinct components akin to personality, memory, thought process, and abilities. Let’s break these down: -![The Github repository](../../../docs/content/imgs/quickstart/t2_03.png) +![The Github repository](..%2F..%2Fdocs/content/imgs/quickstart/t2_03.png) Anatomy of an Agent from the Agent Landscape Survey ### **Profile** diff --git a/forge/tutorials/003_crafting_agent_logic.md b/forge/tutorials/003_crafting_agent_logic.md index c5b7e89a9911..466d42fe5fef 100644 --- a/forge/tutorials/003_crafting_agent_logic.md +++ b/forge/tutorials/003_crafting_agent_logic.md @@ -1,6 +1,6 @@ # AutoGPT Forge: Crafting Intelligent Agent Logic -![Header](../../../docs/content/imgs/quickstart/t3_01.png) +![Header](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_01.png) **By Craig Swift & [Ryan Brandt](https://github.com/paperMoose)** Hey there! Ready for part 3 of our AutoGPT Forge tutorial series? If you missed the earlier parts, catch up here: @@ -17,7 +17,7 @@ Make sure you've set up your project and created an agent as described in our in In the image below, you'll see my "SmartAgent" and the agent.py file inside the 'forge' folder. That's where we'll be adding our LLM-based logic. If you're unsure about the project structure or agent functions from our last guide, don't worry. We'll cover the basics as we go! -![SmartAgent](../../../docs/content/imgs/quickstart/t3_02.png) +![SmartAgent](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_02.png) --- @@ -125,7 +125,7 @@ Now that we've set this up, let's move to the next exciting part: The PromptEngi **The Art of Prompting** -![Prompting 101](../../../docs/content/imgs/quickstart/t3_03.png) +![Prompting 101](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_03.png) Prompting is like shaping messages for powerful language models like ChatGPT. Since these models respond to input details, creating the right prompt can be a challenge. That's where the **PromptEngine** comes in. @@ -479,7 +479,7 @@ d88P 888 "Y88888 "Y888 "Y88P" "Y8888P88 888 888 3. **Navigate to Benchmarking** - Look to the left, and you'll spot a trophy icon. Click it to enter the benchmarking arena. -![Benchmarking page of the AutoGPT UI](../../../docs/content/imgs/quickstart/t3_04.png) +![Benchmarking page of the AutoGPT UI](..%2F..%2F..%2Fdocs/content/imgs/quickstart/t3_04.png) 4. **Select the 'WriteFile' Test** - Choose the 'WriteFile' test from the available options. diff --git a/rnd/autogpt_builder/package.json b/rnd/autogpt_builder/package.json index 193237724c17..559546101423 100644 --- a/rnd/autogpt_builder/package.json +++ b/rnd/autogpt_builder/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.2", + "ajv": "^8.17.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", diff --git a/rnd/autogpt_builder/src/app/globals.css b/rnd/autogpt_builder/src/app/globals.css index 39a92718dd5b..d90551d168c8 100644 --- a/rnd/autogpt_builder/src/app/globals.css +++ b/rnd/autogpt_builder/src/app/globals.css @@ -7,3 +7,60 @@ text-wrap: balance; } } + + + +:root { + --background: 180 100% 95%; + --foreground: 180 5% 0%; + --card: 180 50% 90%; + --card-foreground: 180 5% 10%; + --popover: 180 100% 95%; + --popover-foreground: 180 100% 0%; + --primary: 180 76.5% 30%; + --primary-foreground: 0 0% 100%; + --secondary: 180 30% 70%; + --secondary-foreground: 0 0% 0%; + --muted: 142 30% 85%; + --muted-foreground: 180 5% 35%; + --accent: 142 30% 80%; + --accent-foreground: 180 5% 10%; + --destructive: 0 100% 30%; + --destructive-foreground: 180 5% 90%; + --border: 180 30% 50%; + --input: 180 30% 18%; + --ring: 180 76.5% 30%; + --radius: 0.5rem; +} +.dark { + --background: 180 50% 5%; + --foreground: 180 5% 90%; + --card: 180 50% 0%; + --card-foreground: 180 5% 90%; + --popover: 180 50% 5%; + --popover-foreground: 180 5% 90%; + --primary: 180 76.5% 30%; + --primary-foreground: 0 0% 100%; + --secondary: 180 30% 10%; + --secondary-foreground: 0 0% 100%; + --muted: 142 30% 15%; + --muted-foreground: 180 5% 60%; + --accent: 142 30% 15%; + --accent-foreground: 180 5% 90%; + --destructive: 0 100% 30%; + --destructive-foreground: 180 5% 90%; + --border: 180 30% 18%; + --input: 180 30% 18%; + --ring: 180 76.5% 30%; + --radius: 0.5rem; +} + + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/rnd/autogpt_builder/src/app/layout.tsx b/rnd/autogpt_builder/src/app/layout.tsx index 604100536c75..f7b4dc8ddb72 100644 --- a/rnd/autogpt_builder/src/app/layout.tsx +++ b/rnd/autogpt_builder/src/app/layout.tsx @@ -1,10 +1,8 @@ import React from 'react'; import type { Metadata } from "next"; -import { ThemeProvider as NextThemeProvider } from "next-themes"; -import { type ThemeProviderProps } from "next-themes/dist/types"; import { Inter } from "next/font/google"; import Link from "next/link"; -import { CubeIcon, Pencil1Icon, ReaderIcon, TimerIcon } from "@radix-ui/react-icons"; +import { Pencil1Icon, TimerIcon } from "@radix-ui/react-icons"; import "./globals.css"; @@ -13,6 +11,7 @@ import { Button, buttonVariants } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { Providers } from "@/app/providers"; const inter = Inter({ subsets: ["latin"] }); @@ -21,60 +20,57 @@ export const metadata: Metadata = { description: "Your one stop shop to creating AI Agents", }; -function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children} -} - const NavBar = () => ( - + ); - export default function RootLayout({ - children, + children, }: Readonly<{ children: React.ReactNode; }>) { return ( - + - -
- -
- {children} -
-
-
+ > +
+ +
+ {children} +
+
+ - + ); } diff --git a/rnd/autogpt_builder/src/app/providers.tsx b/rnd/autogpt_builder/src/app/providers.tsx new file mode 100644 index 000000000000..b07beb96d00d --- /dev/null +++ b/rnd/autogpt_builder/src/app/providers.tsx @@ -0,0 +1,14 @@ +'use client' + +import * as React from 'react' +import { ThemeProvider as NextThemesProvider } from 'next-themes' +import { ThemeProviderProps } from 'next-themes/dist/types' +import { TooltipProvider } from '@/components/ui/tooltip' + +export function Providers({ children, ...props }: ThemeProviderProps) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/CustomEdge.tsx b/rnd/autogpt_builder/src/components/CustomEdge.tsx index 5a3b46403bef..d5542647cbaf 100644 --- a/rnd/autogpt_builder/src/components/CustomEdge.tsx +++ b/rnd/autogpt_builder/src/components/CustomEdge.tsx @@ -1,14 +1,22 @@ -import { FC, memo, useMemo } from "react"; -import { BaseEdge, EdgeProps, getBezierPath, XYPosition } from "reactflow"; +import React, { FC, memo, useMemo, useState } from "react"; +import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath, useReactFlow, XYPosition } from "reactflow"; +import './customedge.css'; +import { X } from 'lucide-react'; export type CustomEdgeData = { edgeColor: string sourcePos: XYPosition } -const CustomEdgeFC: FC> = ({ data, selected, source, sourcePosition, sourceX, sourceY, target, targetPosition, targetX, targetY, markerEnd }) => { +const CustomEdgeFC: FC> = ({ id, data, selected, source, sourcePosition, sourceX, sourceY, target, targetPosition, targetX, targetY, markerEnd }) => { + const [isHovered, setIsHovered] = useState(false); + const { setEdges } = useReactFlow(); - const [path] = getBezierPath({ + const onEdgeClick = () => { + setEdges((edges) => edges.filter((edge) => edge.id !== id)); + } + + const [path, labelX, labelY] = getBezierPath({ sourceX: sourceX - 5, sourceY, sourcePosition, @@ -25,12 +33,47 @@ const CustomEdgeFC: FC> = ({ data, selected, source, s `M ${sourceX - 5} ${sourceY} C ${sourceX + 128} ${sourceY - yDifference - 128} ${targetX - 128} ${sourceY - yDifference - 128} ${targetX + 3}, ${targetY}` : path; + console.table({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, path, labelX, labelY }); + return ( - + <> + + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + /> + +
+ +
+
+ ) }; diff --git a/rnd/autogpt_builder/src/components/CustomNode.tsx b/rnd/autogpt_builder/src/components/CustomNode.tsx index e3c4fff6f433..7805669f6409 100644 --- a/rnd/autogpt_builder/src/components/CustomNode.tsx +++ b/rnd/autogpt_builder/src/components/CustomNode.tsx @@ -1,16 +1,17 @@ -import React, { useState, useEffect, FC, memo, useRef } from 'react'; -import { NodeProps } from 'reactflow'; +import React, { useState, useEffect, FC, memo, useCallback } from 'react'; +import { NodeProps, useReactFlow } from 'reactflow'; import 'reactflow/dist/style.css'; import './customnode.css'; import InputModalComponent from './InputModalComponent'; import OutputModalComponent from './OutputModalComponent'; import { BlockSchema } from '@/lib/types'; -import { beautifyString } from '@/lib/utils'; +import { beautifyString, setNestedProperty } from '@/lib/utils'; import { Switch } from "@/components/ui/switch" import NodeHandle from './NodeHandle'; import NodeInputField from './NodeInputField'; +import { Copy, Trash2 } from 'lucide-react'; -type CustomNodeData = { +export type CustomNodeData = { blockType: string; title: string; inputSchema: BlockSchema; @@ -21,6 +22,11 @@ type CustomNodeData = { isOutputOpen: boolean; status?: string; output_data?: any; + block_id: string; + backend_id?: string; + errors?: { [key: string]: string | null }; + setErrors: (errors: { [key: string]: string | null }) => void; + setIsAnyModalOpen?: (isOpen: boolean) => void; }; const CustomNode: FC> = ({ data, id }) => { @@ -29,9 +35,12 @@ const CustomNode: FC> = ({ data, id }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [activeKey, setActiveKey] = useState(null); const [modalValue, setModalValue] = useState(''); - const [errors, setErrors] = useState<{ [key: string]: string | null }>({}); const [isOutputModalOpen, setIsOutputModalOpen] = useState(false); - const outputDataRef = useRef(null); + const [isHovered, setIsHovered] = useState(false); + + + const { getNode, setNodes, getEdges, setEdges } = useReactFlow(); + useEffect(() => { if (data.output_data || data.status) { @@ -40,8 +49,8 @@ const CustomNode: FC> = ({ data, id }) => { }, [data.output_data, data.status]); useEffect(() => { - console.log(`Node ${id} data:`, data); - }, [id, data]); + data.setIsAnyModalOpen?.(isModalOpen || isOutputModalOpen); + }, [isModalOpen, isOutputModalOpen, data]); const toggleOutput = (checked: boolean) => { setIsOutputOpen(checked); @@ -80,11 +89,13 @@ const CustomNode: FC> = ({ data, id }) => { console.log(`Updating hardcoded values for node ${id}:`, newValues); data.setHardcodedValues(newValues); - setErrors((prevErrors) => ({ ...prevErrors, [key]: null })); + const errors = data.errors || {}; + // Remove error with the same key + setNestedProperty(errors, key, null); + data.setErrors({ ...errors }); }; const getValue = (key: string) => { - console.log(`Getting value for key: ${key}`); const keys = key.split('.'); return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues); }; @@ -122,28 +133,6 @@ const CustomNode: FC> = ({ data, id }) => { setActiveKey(null); }; - const validateInputs = () => { - const newErrors: { [key: string]: string | null } = {}; - const validateRecursive = (schema: any, parentKey: string = '') => { - Object.entries(schema.properties).forEach(([key, propSchema]: [string, any]) => { - const fullKey = parentKey ? `${parentKey}.${key}` : key; - const value = getValue(fullKey); - - if (propSchema.type === 'object' && propSchema.properties) { - validateRecursive(propSchema, fullKey); - } else { - if (propSchema.required && !value) { - newErrors[fullKey] = `${fullKey} is required`; - } - } - }); - }; - - validateRecursive(data.inputSchema); - setErrors(newErrors); - return Object.values(newErrors).every((error) => error === null); - }; - const handleOutputClick = () => { setIsOutputModalOpen(true); setModalValue(typeof data.output_data === 'object' ? JSON.stringify(data.output_data, null, 2) : data.output_data); @@ -154,10 +143,86 @@ const CustomNode: FC> = ({ data, id }) => { return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; }; + const handleHovered = () => { + setIsHovered(true); + console.log('isHovered', isHovered); + } + + const handleMouseLeave = () => { + setIsHovered(false); + console.log('isHovered', isHovered); + } + + const deleteNode = useCallback(() => { + console.log('Deleting node:', id); + + // Get all edges connected to this node + const connectedEdges = getEdges().filter(edge => edge.source === id || edge.target === id); + + // For each connected edge, update the connected node's state + connectedEdges.forEach(edge => { + const connectedNodeId = edge.source === id ? edge.target : edge.source; + const connectedNode = getNode(connectedNodeId); + + if (connectedNode) { + setNodes(nodes => nodes.map(node => { + if (node.id === connectedNodeId) { + // Update the node's data to reflect the disconnection + const updatedConnections = node.data.connections.filter( + conn => !(conn.source === id || conn.target === id) + ); + return { + ...node, + data: { + ...node.data, + connections: updatedConnections + } + }; + } + return node; + })); + } + }); + + // Remove the node and its connected edges + setNodes(nodes => nodes.filter(node => node.id !== id)); + setEdges(edges => edges.filter(edge => edge.source !== id && edge.target !== id)); + }, [id, setNodes, setEdges, getNode, getEdges]); + + const copyNode = useCallback(() => { + // This is a placeholder function. The actual copy functionality + // will be implemented by another team member. + console.log('Copy node:', id); + }, [id]); + return ( -
+
{beautifyString(data.blockType?.replace(/Block$/, '') || data.title)}
+
+ {isHovered && ( + <> + + + + )} +
@@ -165,17 +230,17 @@ const CustomNode: FC> = ({ data, id }) => { Object.entries(data.inputSchema.properties).map(([key, schema]) => { const isRequired = data.inputSchema.required?.includes(key); return (isRequired || isAdvancedOpen) && ( -
- - {isHandleConnected(key) ? <> : - } +
{ }}> + + {!isHandleConnected(key) && + }
); })} @@ -196,9 +261,9 @@ const CustomNode: FC> = ({ data, id }) => { const outputText = typeof data.output_data === 'object' ? JSON.stringify(data.output_data) : data.output_data; - + if (!outputText) return 'No output data'; - + return outputText.length > 100 ? `${outputText.slice(0, 100)}... Press To Read More` : outputText; diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index d1311c0dea3f..62c4faafa31b 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -2,12 +2,10 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react'; import ReactFlow, { addEdge, - applyNodeChanges, - applyEdgeChanges, + useNodesState, + useEdgesState, Node, Edge, - OnNodesChange, - OnEdgesChange, OnConnect, NodeTypes, Connection, @@ -15,32 +13,17 @@ import ReactFlow, { MarkerType, } from 'reactflow'; import 'reactflow/dist/style.css'; -import CustomNode from './CustomNode'; +import CustomNode, { CustomNodeData } from './CustomNode'; import './flow.css'; -import AutoGPTServerAPI, { Block, Graph, ObjectSchema } from '@/lib/autogpt-server-api'; +import AutoGPTServerAPI, { Block, Graph, NodeExecutionResult, ObjectSchema } from '@/lib/autogpt-server-api'; import { Button } from './ui/button'; import { Input } from './ui/input'; import { ChevronRight, ChevronLeft } from "lucide-react"; -import { deepEquals, getTypeColor } from '@/lib/utils'; +import { deepEquals, getTypeColor, removeEmptyStringsAndNulls, setNestedProperty } from '@/lib/utils'; import { beautifyString } from '@/lib/utils'; import { CustomEdge, CustomEdgeData } from './CustomEdge'; import ConnectionLine from './ConnectionLine'; - - -type CustomNodeData = { - blockType: string; - title: string; - inputSchema: ObjectSchema; - outputSchema: ObjectSchema; - hardcodedValues: { [key: string]: any }; - setHardcodedValues: (values: { [key: string]: any }) => void; - connections: Array<{ source: string; sourceHandle: string; target: string; targetHandle: string }>; - isOutputOpen: boolean; - status?: string; - output_data?: any; - block_id: string; - backend_id?: string; -}; +import Ajv from 'ajv'; const Sidebar: React.FC<{ isOpen: boolean, availableNodes: Block[], addNode: (id: string, name: string) => void }> = ({ isOpen, availableNodes, addNode }) => { @@ -71,13 +54,15 @@ const Sidebar: React.FC<{ isOpen: boolean, availableNodes: Block[], addNode: (id ); }; +const ajv = new Ajv({ strict: false, allErrors: true }); + const FlowEditor: React.FC<{ flowID?: string; template?: boolean; className?: string; }> = ({ flowID, template, className }) => { - const [nodes, setNodes] = useState[]>([]); - const [edges, setEdges] = useState[]>([]); + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [nodeId, setNodeId] = useState(1); const [availableNodes, setAvailableNodes] = useState([]); const [isSidebarOpen, setIsSidebarOpen] = useState(true); @@ -86,6 +71,7 @@ const FlowEditor: React.FC<{ const [agentName, setAgentName] = useState(''); const [copiedNodes, setCopiedNodes] = useState[]>([]); const [copiedEdges, setCopiedEdges] = useState[]>([]); + const [isAnyModalOpen, setIsAnyModalOpen] = useState(false); // Track if any modal is open const apiUrl = process.env.AGPT_SERVER_URL!; const api = useMemo(() => new AutoGPTServerAPI(apiUrl), [apiUrl]); @@ -124,16 +110,6 @@ const FlowEditor: React.FC<{ const nodeTypes: NodeTypes = useMemo(() => ({ custom: CustomNode }), []); const edgeTypes: EdgeTypes = useMemo(() => ({ custom: CustomEdge }), []); - const onNodesChange: OnNodesChange = useCallback( - (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), - [] - ); - - const onEdgesChange: OnEdgesChange = useCallback( - (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), - [] - ); - const getOutputType = (id: string, handleId: string) => { const node = nodes.find((node) => node.id === id); if (!node) return 'unknown'; @@ -237,6 +213,14 @@ const FlowEditor: React.FC<{ connections: [], isOutputOpen: false, block_id: blockId, + setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function + setErrors: (errors: { [key: string]: string | null }) => { + setNodes((nds) => nds.map((node) => + node.id === newNode.id + ? { ...node, data: { ...node.data, errors } } + : node + )); + } }, }; @@ -251,11 +235,12 @@ const FlowEditor: React.FC<{ setNodes(graph.nodes.map(node => { const block = availableNodes.find(block => block.id === node.block_id)!; - const newNode = { + const newNode: Node = { id: node.id, type: 'custom', position: { x: node.metadata.position.x, y: node.metadata.position.y }, data: { + setIsAnyModalOpen: setIsAnyModalOpen, block_id: block.id, blockType: block.name, title: `${block.name} ${node.id}`, @@ -268,8 +253,23 @@ const FlowEditor: React.FC<{ : node )); }, - connections: [], + connections: graph.links + .filter(l => [l.source_id, l.sink_id].includes(node.id)) + .map(link => ({ + source: link.source_id, + sourceHandle: link.source_name, + target: link.sink_id, + targetHandle: link.sink_name, + })), isOutputOpen: false, + setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function + setErrors: (errors: { [key: string]: string | null }) => { + setNodes((nds) => nds.map((node) => + node.id === newNode.id + ? { ...node, data: { ...node.data, errors } } + : node + )); + } }, }; return newNode; @@ -328,12 +328,13 @@ const FlowEditor: React.FC<{ return inputData; }; - async function saveAgent (asTemplate: boolean = false) { + async function saveAgent(asTemplate: boolean = false) { setNodes((nds) => nds.map((node) => ({ ...node, data: { ...node.data, + hardcodedValues: removeEmptyStringsAndNulls(node.data.hardcodedValues), status: undefined, }, })) @@ -368,6 +369,10 @@ const FlowEditor: React.FC<{ input_default: inputDefault, input_nodes: inputNodes, output_nodes: outputNodes, + data: { + ...node.data, + hardcodedValues: removeEmptyStringsAndNulls(node.data.hardcodedValues), + }, metadata: { position: node.position } }; }); @@ -396,7 +401,7 @@ const FlowEditor: React.FC<{ const newSavedAgent = savedAgent ? await (savedAgent.is_template - ? api.updateTemplate(savedAgent.id, payload) + ? api.updateTemplate(savedAgent.id, payload) : api.updateGraph(savedAgent.id, payload)) : await (asTemplate ? api.createTemplate(payload) @@ -427,6 +432,40 @@ const FlowEditor: React.FC<{ return newSavedAgent.id; }; + const validateNodes = (): boolean => { + let isValid = true; + + nodes.forEach(node => { + const validate = ajv.compile(node.data.inputSchema); + const errors = {} as { [key: string]: string | null }; + + // Validate values against schema using AJV + const valid = validate(node.data.hardcodedValues); + if (!valid) { + // Populate errors if validation fails + validate.errors?.forEach((error) => { + // Skip error if there's an edge connected + const handle = error.instancePath.split(/[\/.]/)[0]; + if (node.data.connections.some(conn => conn.target === node.id || conn.targetHandle === handle)) { + return; + } + isValid = false; + if (error.instancePath && error.message) { + const key = error.instancePath.slice(1); + console.log("Error", key, error.message); + setNestedProperty(errors, key, error.message[0].toUpperCase() + error.message.slice(1)); + } else if (error.keyword === "required") { + const key = error.params.missingProperty; + setNestedProperty(errors, key, "This field is required"); + } + }); + } + node.data.setErrors(errors); + }); + + return isValid; + }; + const runAgent = async () => { try { const newAgentId = await saveAgent(); @@ -435,6 +474,11 @@ const FlowEditor: React.FC<{ return; } + if (!validateNodes()) { + console.error('Validation failed; aborting run'); + return; + } + api.subscribeToExecution(newAgentId); api.runGraph(newAgentId); @@ -443,9 +487,7 @@ const FlowEditor: React.FC<{ } }; - - - const updateNodesWithExecutionData = (executionData: any[]) => { + const updateNodesWithExecutionData = (executionData: NodeExecutionResult[]) => { setNodes((nds) => nds.map((node) => { const nodeExecution = executionData.find((exec) => exec.node_id === node.data.backend_id); @@ -468,6 +510,8 @@ const FlowEditor: React.FC<{ const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); const handleKeyDown = useCallback((event: KeyboardEvent) => { + if (isAnyModalOpen) return; // Prevent copy/paste if any modal is open + if (event.ctrlKey || event.metaKey) { if (event.key === 'c' || event.key === 'C') { // Copy selected nodes @@ -479,30 +523,48 @@ const FlowEditor: React.FC<{ if (event.key === 'v' || event.key === 'V') { // Paste copied nodes if (copiedNodes.length > 0) { - const newNodes = copiedNodes.map((node, index) => ({ - ...node, - id: (nodeId + index).toString(), - position: { - x: node.position.x + 20, // Offset pasted nodes - y: node.position.y + 20, - }, - selected: true, // Select the new nodes - })); + const newNodes = copiedNodes.map((node, index) => { + const newNodeId = (nodeId + index).toString(); + return { + ...node, + id: newNodeId, + position: { + x: node.position.x + 20, // Offset pasted nodes + y: node.position.y + 20, + }, + data: { + ...node.data, + status: undefined, // Reset status + output_data: undefined, // Clear output data + setHardcodedValues: (values: { [key: string]: any }) => { + setNodes((nds) => nds.map((n) => + n.id === newNodeId + ? { ...n, data: { ...n.data, hardcodedValues: values } } + : n + )); + }, + }, + }; + }); const updatedNodes = nodes.map(node => ({ ...node, selected: false })); // Deselect old nodes setNodes([...updatedNodes, ...newNodes]); setNodeId(prevId => prevId + copiedNodes.length); - // Optionally, you can handle edges similarly - const newEdges = copiedEdges.map(edge => ({ - ...edge, - id: `${edge.source}_${edge.sourceHandle}_${edge.target}_${edge.targetHandle}_${Date.now()}`, - source: (parseInt(edge.source) + copiedNodes.length).toString(), - target: (parseInt(edge.target) + copiedNodes.length).toString(), - })); + + const newEdges = copiedEdges.map(edge => { + const newSourceId = newNodes.find(n => n.data.title === edge.source)?.id || edge.source; + const newTargetId = newNodes.find(n => n.data.title === edge.target)?.id || edge.target; + return { + ...edge, + id: `${newSourceId}_${edge.sourceHandle}_${newTargetId}_${edge.targetHandle}_${Date.now()}`, + source: newSourceId, + target: newTargetId, + }; + }); setEdges([...edges, ...newEdges]); } } } - }, [nodes, edges, copiedNodes, copiedEdges, nodeId]); + }, [nodes, edges, copiedNodes, copiedEdges, nodeId, isAnyModalOpen]); useEffect(() => { window.addEventListener('keydown', handleKeyDown); @@ -529,7 +591,7 @@ const FlowEditor: React.FC<{ ({ ...node, data: { ...node.data, setIsAnyModalOpen } }))} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} diff --git a/rnd/autogpt_builder/src/components/NodeHandle.tsx b/rnd/autogpt_builder/src/components/NodeHandle.tsx index 46516e763734..381a76d51fda 100644 --- a/rnd/autogpt_builder/src/components/NodeHandle.tsx +++ b/rnd/autogpt_builder/src/components/NodeHandle.tsx @@ -8,10 +8,11 @@ type HandleProps = { keyName: string, schema: BlockSchema, isConnected: boolean, + isRequired?: boolean, side: 'left' | 'right' } -const NodeHandle: FC = ({ keyName, isConnected, schema, side }) => { +const NodeHandle: FC = ({ keyName, schema, isConnected, isRequired, side }) => { const typeName: Record = { string: 'text', @@ -26,7 +27,9 @@ const NodeHandle: FC = ({ keyName, isConnected, schema, side }) => const label = (
- {schema.title || beautifyString(keyName)} + + {schema.title || beautifyString(keyName)}{isRequired ? '*' : ''} + {typeName[schema.type]}
); diff --git a/rnd/autogpt_builder/src/components/NodeInputField.tsx b/rnd/autogpt_builder/src/components/NodeInputField.tsx index f2b81fda6aeb..583d0332782f 100644 --- a/rnd/autogpt_builder/src/components/NodeInputField.tsx +++ b/rnd/autogpt_builder/src/components/NodeInputField.tsx @@ -10,7 +10,7 @@ type BlockInputFieldProps = { value: string | Array | { [key: string]: string } handleInputClick: (key: string) => void handleInputChange: (key: string, value: any) => void - errors: { [key: string]: string | null } + errors?: { [key: string]: string } | string | null } const NodeInputField: FC = @@ -20,7 +20,7 @@ const NodeInputField: FC = const [keyValuePairs, setKeyValuePairs] = useState<{ key: string, value: string }[]>([]); const fullKey = parentKey ? `${parentKey}.${key}` : key; - const error = errors[fullKey]; + const error = typeof errors === 'string' ? errors : errors?.[key] ?? ""; const displayKey = schema.title || beautifyString(key); const handleAddProperty = () => { @@ -35,14 +35,15 @@ const NodeInputField: FC = }; const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => { + const className = `clickable-input ${error ? 'border-error' : ''}` // if secret is true, then the input field will be a password field if the value is not null return secret ? ( -
handleInputClick(fullKey)}> - {value ? ******** : {placeholder}} +
handleInputClick(fullKey)}> + {value ? ******** : {placeholder}}
) : ( -
handleInputClick(fullKey)}> +
handleInputClick(fullKey)}> {value || {placeholder}}
) @@ -247,11 +248,11 @@ const NodeInputField: FC = case 'integer': return (
- handleInputChange(fullKey, parseFloat(e.target.value))} - className="number-input" + className={`number-input ${error ? 'border-error' : ''}`} /> {error && {error}}
@@ -263,7 +264,7 @@ const NodeInputField: FC =
{arrayValues.map((item: string, index: number) => (
- handleInputChange(`${fullKey}.${index}`, e.target.value)} @@ -277,7 +278,7 @@ const NodeInputField: FC = - {error && {error}} + {error && {error}}
); } diff --git a/rnd/autogpt_builder/src/components/customedge.css b/rnd/autogpt_builder/src/components/customedge.css new file mode 100644 index 000000000000..a666477d97ca --- /dev/null +++ b/rnd/autogpt_builder/src/components/customedge.css @@ -0,0 +1,38 @@ +.edge-label-renderer { + position: absolute; + pointer-events: all; +} + +.edge-label-button { + width: 20px; + height: 20px; + background: #eee; + border: 1px solid #fff; + cursor: pointer; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + padding: 0; + color: #555; + opacity: 0; + transition: opacity 0.2s ease-in-out, background-color 0.2s ease-in-out; +} + +.edge-label-button.visible { + opacity: 1; +} + +.edge-label-button:hover { + box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.08); + background: #f0f0f0; +} + +.edge-label-button svg { + width: 14px; + height: 14px; +} + +.react-flow__edge-interaction { + cursor: pointer; +} \ No newline at end of file diff --git a/rnd/autogpt_builder/src/components/customnode.css b/rnd/autogpt_builder/src/components/customnode.css index 0802dd7e1543..c6968fed739a 100644 --- a/rnd/autogpt_builder/src/components/customnode.css +++ b/rnd/autogpt_builder/src/components/customnode.css @@ -2,7 +2,7 @@ padding: 15px; border: 3px solid #4b5563; border-radius: 12px; - background: #ffffff; /* White background */ + background: #ffffff; color: #000000; width: 500px; box-sizing: border-box; @@ -16,6 +16,60 @@ gap: 1px; } +.custom-node .mb-2 { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 40px; + /* Increased to accommodate larger buttons */ + margin-bottom: 10px; +} + +.custom-node .mb-2 .text-lg { + flex-grow: 1; + margin-right: 10px; +} + +.node-actions { + display: flex; + gap: 5px; +} + +.node-action-button { + width: 32px; + /* Increased size */ + height: 32px; + /* Increased size */ + display: flex; + align-items: center; + justify-content: center; + background-color: #f3f4f6; + /* Light gray background */ + border: 1px solid #d1d5db; + /* Light border */ + border-radius: 6px; + color: #4b5563; + transition: all 0.2s ease-in-out; + cursor: pointer; +} + +.node-action-button:hover { + background-color: #e5e7eb; + color: #1f2937; +} + +.node-action-button:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); +} + +.node-action-button svg { + width: 18px; + /* Increased icon size */ + height: 18px; + /* Increased icon size */ +} +/* Existing styles */ .handle-container { display: flex; position: relative; @@ -54,6 +108,10 @@ position: relative; } +.border-error { + border: 1px solid #d9534f; +} + .clickable-input span { display: inline-block; white-space: nowrap; @@ -82,7 +140,6 @@ width: 100%; padding: 5px; border-radius: 4px; - border: 1px solid #000; background: #fff; color: #000; } @@ -172,6 +229,14 @@ border-color: #c0392b; /* Red border for failed nodes */ } +.incomplete { + border-color: #9f14ab; /* Pink border for incomplete nodes */ +} + +.queued { + border-color: #25e6e6; /* Cyanic border for failed nodes */ +} + .custom-switch { padding-left: 2px; } diff --git a/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts b/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts index 61b920a0cd95..6901326ab17e 100644 --- a/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts +++ b/rnd/autogpt_builder/src/lib/autogpt-server-api/client.ts @@ -182,7 +182,9 @@ export default class AutoGPTServerAPI { } } - sendWebSocketMessage(method: string, data: any) { + sendWebSocketMessage( + method: M, data: WebsocketMessageTypeMap[M] + ) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify({ method, data })); } else { @@ -190,7 +192,9 @@ export default class AutoGPTServerAPI { } } - onWebSocketMessage(method: string, handler: (data: any) => void) { + onWebSocketMessage( + method: M, handler: (data: WebsocketMessageTypeMap[M]) => void + ) { this.messageHandlers[method] = handler; } @@ -198,7 +202,7 @@ export default class AutoGPTServerAPI { this.sendWebSocketMessage('subscribe', { graph_id: graphId }); } - runGraph(graphId: string, data: any = {}) { + runGraph(graphId: string, data: WebsocketMessageTypeMap["run_graph"]["data"] = {}) { this.sendWebSocketMessage('run_graph', { graph_id: graphId, data }); } } @@ -212,3 +216,9 @@ type GraphCreateRequestBody = { } | { graph: GraphCreatable; } + +type WebsocketMessageTypeMap = { + subscribe: { graph_id: string; }; + run_graph: { graph_id: string; data: { [key: string]: any }; }; + execution_event: NodeExecutionResult; +} diff --git a/rnd/autogpt_builder/src/lib/utils.ts b/rnd/autogpt_builder/src/lib/utils.ts index f4e24d59eaa3..a8968085d1a9 100644 --- a/rnd/autogpt_builder/src/lib/utils.ts +++ b/rnd/autogpt_builder/src/lib/utils.ts @@ -119,3 +119,40 @@ export function exportAsJSONFile(obj: object, filename: string): void { // Clean up URL.revokeObjectURL(url); } + +export function setNestedProperty(obj: any, path: string, value: any) { + const keys = path.split(/[\/.]/); // Split by / or . + let current = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (!current[key] || typeof current[key] !== 'object') { + current[key] = {}; + } + current = current[key]; + } + + current[keys[keys.length - 1]] = value; +} + +export function removeEmptyStringsAndNulls(obj: any): any { + if (Array.isArray(obj)) { + // If obj is an array, recursively remove empty strings and nulls from its elements + return obj + .map(item => removeEmptyStringsAndNulls(item)) + .filter(item => item !== null && (typeof item !== 'string' || item.trim() !== '')); + } else if (typeof obj === 'object' && obj !== null) { + // If obj is an object, recursively remove empty strings and nulls from its properties + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + const value = obj[key]; + if (value === null || (typeof value === 'string' && value.trim() === '')) { + delete obj[key]; + } else { + obj[key] = removeEmptyStringsAndNulls(value); + } + } + } + } + return obj; +} diff --git a/rnd/autogpt_builder/tailwind.config.ts b/rnd/autogpt_builder/tailwind.config.ts index 1becdec7d3ab..9784068cc0e9 100644 --- a/rnd/autogpt_builder/tailwind.config.ts +++ b/rnd/autogpt_builder/tailwind.config.ts @@ -15,21 +15,65 @@ const config = { }, }, extend: { - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, + fontFamily: { + sans: ['var(--font-geist-sans)'], + mono: ['var(--font-geist-mono)'] + }, + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + } }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' }, - }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--radix-accordion-content-height)' } + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: '0' } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } }, plugins: [require("tailwindcss-animate")], } satisfies Config; diff --git a/rnd/autogpt_builder/yarn.lock b/rnd/autogpt_builder/yarn.lock index fc24fc21fb8b..768df16b2ae5 100644 --- a/rnd/autogpt_builder/yarn.lock +++ b/rnd/autogpt_builder/yarn.lock @@ -997,6 +997,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -2075,6 +2085,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2703,6 +2718,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -3712,6 +3732,11 @@ remark-rehype@^11.0.0: unified "^11.0.0" vfile "^6.0.0" +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" diff --git a/rnd/autogpt_server/.gitignore b/rnd/autogpt_server/.gitignore index 7fd0341ba08e..3e94e20ac356 100644 --- a/rnd/autogpt_server/.gitignore +++ b/rnd/autogpt_server/.gitignore @@ -1,5 +1,7 @@ database.db database.db-journal +dev.db +dev.db-journal build/ config.json secrets/* diff --git a/rnd/autogpt_server/.vscode/settings.json b/rnd/autogpt_server/.vscode/settings.json new file mode 100644 index 000000000000..31f7f02be464 --- /dev/null +++ b/rnd/autogpt_server/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.typeCheckingMode": "basic", +} diff --git a/rnd/autogpt_server/README.advanced.md b/rnd/autogpt_server/README.advanced.md index b7133b3e61da..a452e73f2d14 100644 --- a/rnd/autogpt_server/README.advanced.md +++ b/rnd/autogpt_server/README.advanced.md @@ -31,9 +31,9 @@ We use the Poetry to manage the dependencies. To set up the project, follow thes 4. Copy .env.example to .env ```sh - cp env.example .env + cp .env.example .env ``` - + 5. Generate the Prisma client ```sh @@ -72,4 +72,4 @@ Run the following command: ```sh poetry run app -``` \ No newline at end of file +``` diff --git a/rnd/autogpt_server/autogpt_server/app.py b/rnd/autogpt_server/autogpt_server/app.py index fa090a579e1d..57141bfb35b9 100644 --- a/rnd/autogpt_server/autogpt_server/app.py +++ b/rnd/autogpt_server/autogpt_server/app.py @@ -28,14 +28,13 @@ def run_processes(processes: list[AppProcess], **kwargs): def main(**kwargs): - settings = get_config_and_secrets() set_start_method("spawn", force=True) freeze_support() run_processes( [ PyroNameServer(), - ExecutionManager(pool_size=settings.config.num_workers), + ExecutionManager(), ExecutionScheduler(), AgentServer(), ], diff --git a/rnd/autogpt_server/autogpt_server/blocks/__init__.py b/rnd/autogpt_server/autogpt_server/blocks/__init__.py index 51c235403999..35319412a005 100644 --- a/rnd/autogpt_server/autogpt_server/blocks/__init__.py +++ b/rnd/autogpt_server/autogpt_server/blocks/__init__.py @@ -29,6 +29,9 @@ if block.id in AVAILABLE_BLOCKS: raise ValueError(f"Block ID {block.name} error: {block.id} is already in use") + if block.disabled: + continue + AVAILABLE_BLOCKS[block.id] = block __all__ = ["AVAILABLE_MODULES", "AVAILABLE_BLOCKS"] diff --git a/rnd/autogpt_server/autogpt_server/blocks/block.py b/rnd/autogpt_server/autogpt_server/blocks/block.py index 5a274c4f2dfe..168460fabfe8 100644 --- a/rnd/autogpt_server/autogpt_server/blocks/block.py +++ b/rnd/autogpt_server/autogpt_server/blocks/block.py @@ -29,6 +29,7 @@ def __init__(self): categories={BlockCategory.BASIC}, input_schema=BlockInstallationBlock.Input, output_schema=BlockInstallationBlock.Output, + disabled=True, ) def run(self, input_data: Input) -> BlockOutput: diff --git a/rnd/autogpt_server/autogpt_server/blocks/if_block.py b/rnd/autogpt_server/autogpt_server/blocks/if_block.py new file mode 100644 index 000000000000..c28f2631f274 --- /dev/null +++ b/rnd/autogpt_server/autogpt_server/blocks/if_block.py @@ -0,0 +1,101 @@ +from enum import Enum +from typing import Any + +from autogpt_server.data.block import Block, BlockOutput, BlockSchema +from autogpt_server.data.model import SchemaField + + +class ComparisonOperator(Enum): + EQUAL = "==" + NOT_EQUAL = "!=" + GREATER_THAN = ">" + LESS_THAN = "<" + GREATER_THAN_OR_EQUAL = ">=" + LESS_THAN_OR_EQUAL = "<=" + + +class ConditionBlock(Block): + class Input(BlockSchema): + value1: Any = SchemaField( + description="Enter the first value for comparison", + placeholder="For example: 10 or 'hello' or True", + ) + operator: ComparisonOperator = SchemaField( + description="Choose the comparison operator", + placeholder="Select an operator", + ) + value2: Any = SchemaField( + description="Enter the second value for comparison", + placeholder="For example: 20 or 'world' or False", + ) + yes_value: Any = SchemaField( + description="(Optional) Value to output if the condition is true. If not provided, value1 will be used.", + placeholder="Leave empty to use value1, or enter a specific value", + default=None, + ) + no_value: Any = SchemaField( + description="(Optional) Value to output if the condition is false. If not provided, value1 will be used.", + placeholder="Leave empty to use value1, or enter a specific value", + default=None, + ) + + class Output(BlockSchema): + result: bool = SchemaField( + description="The result of the condition evaluation (True or False)" + ) + yes_output: Any = SchemaField( + description="The output value if the condition is true" + ) + no_output: Any = SchemaField( + description="The output value if the condition is false" + ) + + def __init__(self): + super().__init__( + id="715696a0-e1da-45c8-b209-c2fa9c3b0be6", + input_schema=ConditionBlock.Input, + output_schema=ConditionBlock.Output, + description="Handles conditional logic based on comparison operators", + test_input={ + "value1": 10, + "operator": ComparisonOperator.GREATER_THAN.value, + "value2": 5, + "yes_value": "Greater", + "no_value": "Not greater", + }, + test_output=[ + ("result", True), + ("yes_output", "Greater"), + ], + ) + + def run(self, input_data: Input) -> BlockOutput: + value1 = input_data.value1 + operator = input_data.operator + value2 = input_data.value2 + yes_value = input_data.yes_value if input_data.yes_value is not None else value1 + no_value = input_data.no_value if input_data.no_value is not None else value1 + + comparison_funcs = { + ComparisonOperator.EQUAL: lambda a, b: a == b, + ComparisonOperator.NOT_EQUAL: lambda a, b: a != b, + ComparisonOperator.GREATER_THAN: lambda a, b: a > b, + ComparisonOperator.LESS_THAN: lambda a, b: a < b, + ComparisonOperator.GREATER_THAN_OR_EQUAL: lambda a, b: a >= b, + ComparisonOperator.LESS_THAN_OR_EQUAL: lambda a, b: a <= b, + } + + try: + result = comparison_funcs[operator](value1, value2) + + yield "result", result + + if result: + yield "yes_output", yes_value + else: + yield "no_output", no_value + + except Exception: + yield "result", None + yield "yes_output", None + yield "no_output", None diff --git a/rnd/autogpt_server/autogpt_server/blocks/maths.py b/rnd/autogpt_server/autogpt_server/blocks/maths.py index 6f06d7c6f3da..02ac91042ed0 100644 --- a/rnd/autogpt_server/autogpt_server/blocks/maths.py +++ b/rnd/autogpt_server/autogpt_server/blocks/maths.py @@ -1,8 +1,6 @@ import operator from enum import Enum -from typing import Any, Union - -import pydantic +from typing import Any from autogpt_server.data.block import Block, BlockOutput, BlockSchema from autogpt_server.data.model import SchemaField @@ -16,17 +14,6 @@ class Operation(Enum): POWER = "Power" -class MathsResult(pydantic.BaseModel): - result: Union[float, int] | None = None - explanation: str - - -class CounterResult(pydantic.BaseModel): - count: int | None = None - type: str - explanation: str - - class MathsBlock(Block): class Input(BlockSchema): operation: Operation = SchemaField( @@ -45,9 +32,7 @@ class Input(BlockSchema): ) class Output(BlockSchema): - result: MathsResult = SchemaField( - description="The result of your calculation with an explanation" - ) + result: float = SchemaField(description="The result of your calculation") def __init__(self): super().__init__( @@ -61,12 +46,7 @@ def __init__(self): "round_result": False, }, test_output=[ - ( - "result", - MathsResult( - result=15.0, explanation="Added 10.0 and 5.0 to get 15.0" - ), - ), + ("result", 15.0), ], ) @@ -76,14 +56,14 @@ def run(self, input_data: Input) -> BlockOutput: b = input_data.b operations = { - Operation.ADD: (operator.add, "Added"), - Operation.SUBTRACT: (operator.sub, "Subtracted"), - Operation.MULTIPLY: (operator.mul, "Multiplied"), - Operation.DIVIDE: (operator.truediv, "Divided"), - Operation.POWER: (operator.pow, "Raised"), + Operation.ADD: operator.add, + Operation.SUBTRACT: operator.sub, + Operation.MULTIPLY: operator.mul, + Operation.DIVIDE: operator.truediv, + Operation.POWER: operator.pow, } - op_func, op_word = operations[operation] + op_func = operations[operation] try: if operation == Operation.DIVIDE and b == 0: @@ -91,27 +71,15 @@ def run(self, input_data: Input) -> BlockOutput: result = op_func(a, b) - if operation == Operation.POWER: - explanation = f"{op_word} {a} to the power of {b} to get {result}" - elif operation == Operation.DIVIDE: - explanation = f"{op_word} {a} by {b} to get {result}" - else: - explanation = f"{op_word} {a} and {b} to get {result}" - if input_data.round_result: result = round(result) - explanation += " (rounded to the nearest whole number)" - yield "result", MathsResult(result=result, explanation=explanation) + yield "result", result except ZeroDivisionError: - yield "result", MathsResult( - result=None, explanation="Cannot divide by zero" - ) - except Exception as e: - yield "result", MathsResult( - result=None, explanation=f"An error occurred: {str(e)}" - ) + yield "result", float("inf") # Return infinity for division by zero + except Exception: + yield "result", float("nan") # Return NaN for other errors class CounterBlock(Block): @@ -122,7 +90,7 @@ class Input(BlockSchema): ) class Output(BlockSchema): - result: CounterResult = SchemaField(description="The result of the count") + count: int = SchemaField(description="The number of items in the collection") def __init__(self): super().__init__( @@ -131,12 +99,7 @@ def __init__(self): output_schema=CounterBlock.Output, test_input={"collection": [1, 2, 3, 4, 5]}, test_output=[ - ( - "result", - CounterResult( - count=5, type="list", explanation="Counted 5 items in a list" - ), - ), + ("count", 5), ], ) @@ -146,27 +109,12 @@ def run(self, input_data: Input) -> BlockOutput: try: if isinstance(collection, (str, list, tuple, set, dict)): count = len(collection) - collection_type = type(collection).__name__ elif hasattr(collection, "__iter__"): count = sum(1 for _ in collection) - collection_type = "iterable" else: raise ValueError("Input is not a countable collection") - if isinstance(collection, str): - item_word = "character" if count == 1 else "characters" - elif isinstance(collection, dict): - item_word = "key-value pair" if count == 1 else "key-value pairs" - else: - item_word = "item" if count == 1 else "items" - - explanation = f"Counted {count} {item_word} in a {collection_type}" - - yield "result", CounterResult( - count=count, type=collection_type, explanation=explanation - ) + yield "count", count - except Exception as e: - yield "result", CounterResult( - count=None, type="error", explanation=f"An error occurred: {str(e)}" - ) + except Exception: + yield "count", -1 # Return -1 to indicate an error diff --git a/rnd/autogpt_server/autogpt_server/data/block.py b/rnd/autogpt_server/autogpt_server/data/block.py index b3747c0ea653..092a531db243 100644 --- a/rnd/autogpt_server/autogpt_server/data/block.py +++ b/rnd/autogpt_server/autogpt_server/data/block.py @@ -124,6 +124,7 @@ def __init__( test_input: BlockInput | list[BlockInput] | None = None, test_output: BlockData | list[BlockData] | None = None, test_mock: dict[str, Any] | None = None, + disabled: bool = False, ): """ Initialize the block with the given schema. @@ -132,11 +133,14 @@ def __init__( id: The unique identifier for the block, this value will be persisted in the DB. So it should be a unique and constant across the application run. Use the UUID format for the ID. + description: The description of the block, explaining what the block does. + contributors: The list of contributors who contributed to the block. input_schema: The schema, defined as a Pydantic model, for the input data. output_schema: The schema, defined as a Pydantic model, for the output data. test_input: The list or single sample input data for the block, for testing. test_output: The list or single expected output if the test_input is run. test_mock: function names on the block implementation to mock on test run. + disabled: If the block is disabled, it will not be available for execution. """ self.id = id self.input_schema = input_schema @@ -147,6 +151,7 @@ def __init__( self.description = description self.categories = categories or set() self.contributors = contributors or set() + self.disabled = disabled @abstractmethod def run(self, input_data: BlockSchemaInputType) -> BlockOutput: diff --git a/rnd/autogpt_server/autogpt_server/data/execution.py b/rnd/autogpt_server/autogpt_server/data/execution.py index 09cd51339266..6447ad8710ca 100644 --- a/rnd/autogpt_server/autogpt_server/data/execution.py +++ b/rnd/autogpt_server/autogpt_server/data/execution.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from enum import Enum from multiprocessing import Manager -from typing import Any +from typing import Any, Generic, TypeVar from prisma.models import ( AgentGraphExecution, @@ -16,6 +16,11 @@ from autogpt_server.util import json +class GraphExecution(BaseModel): + graph_exec_id: str + start_node_execs: list["NodeExecution"] + + class NodeExecution(BaseModel): graph_exec_id: str node_exec_id: str @@ -31,7 +36,10 @@ class ExecutionStatus(str, Enum): FAILED = "FAILED" -class ExecutionQueue: +T = TypeVar("T") + + +class ExecutionQueue(Generic[T]): """ Queue for managing the execution of agents. This will be shared between different processes @@ -40,11 +48,11 @@ class ExecutionQueue: def __init__(self): self.queue = Manager().Queue() - def add(self, execution: NodeExecution) -> NodeExecution: + def add(self, execution: T) -> T: self.queue.put(execution) return execution - def get(self) -> NodeExecution: + def get(self) -> T: return self.queue.get() def empty(self) -> bool: @@ -67,9 +75,14 @@ class ExecutionResult(BaseModel): @staticmethod def from_db(execution: AgentNodeExecution): - input_data: BlockInput = defaultdict() - for data in execution.Input or []: - input_data[data.name] = json.loads(data.data) + if execution.executionData: + # Execution that has been queued for execution will persist its data. + input_data = json.loads(execution.executionData) + else: + # For incomplete execution, executionData will not be yet available. + input_data: BlockInput = defaultdict() + for data in execution.Input or []: + input_data[data.name] = json.loads(data.data) output_data: CompletedBlockOutput = defaultdict(list) for data in execution.Output or []: @@ -146,45 +159,68 @@ async def upsert_execution_input( node_id: str, graph_exec_id: str, input_name: str, - data: Any, -) -> str: + input_data: Any, + node_exec_id: str | None = None, +) -> tuple[str, BlockInput]: """ Insert AgentNodeExecutionInputOutput record for as one of AgentNodeExecution.Input. If there is no AgentNodeExecution that has no `input_name` as input, create new one. + Args: + node_id: The id of the AgentNode. + graph_exec_id: The id of the AgentGraphExecution. + input_name: The name of the input data. + input_data: The input data to be inserted. + node_exec_id: [Optional] The id of the AgentNodeExecution that has no `input_name` as input. If not provided, it will find the eligible incomplete AgentNodeExecution or create a new one. + Returns: - The id of the created or existing AgentNodeExecution. + * The id of the created or existing AgentNodeExecution. + * Dict of node input data, key is the input name, value is the input data. """ existing_execution = await AgentNodeExecution.prisma().find_first( where={ # type: ignore + **({"id": node_exec_id} if node_exec_id else {}), "agentNodeId": node_id, "agentGraphExecutionId": graph_exec_id, + "executionStatus": ExecutionStatus.INCOMPLETE, "Input": {"every": {"name": {"not": input_name}}}, }, order={"addedTime": "asc"}, + include={"Input": True}, ) - json_data = json.dumps(data) + json_input_data = json.dumps(input_data) if existing_execution: await AgentNodeExecutionInputOutput.prisma().create( data={ "name": input_name, - "data": json_data, + "data": json_input_data, "referencedByInputExecId": existing_execution.id, } ) - return existing_execution.id + return existing_execution.id, { + **{ + input_data.name: json.loads(input_data.data) + for input_data in existing_execution.Input or [] + }, + input_name: input_data, + } - else: + elif not node_exec_id: result = await AgentNodeExecution.prisma().create( data={ "agentNodeId": node_id, "agentGraphExecutionId": graph_exec_id, "executionStatus": ExecutionStatus.INCOMPLETE, - "Input": {"create": {"name": input_name, "data": json_data}}, + "Input": {"create": {"name": input_name, "data": json_input_data}}, } ) - return result.id + return result.id, {input_name: input_data} + + else: + raise ValueError( + f"NodeExecution {node_exec_id} not found or already has input {input_name}." + ) async def upsert_execution_output( @@ -205,8 +241,11 @@ async def upsert_execution_output( async def update_execution_status( - node_exec_id: str, status: ExecutionStatus + node_exec_id: str, status: ExecutionStatus, execution_data: BlockInput | None = None ) -> ExecutionResult: + if status == ExecutionStatus.QUEUED and execution_data is None: + raise ValueError("Execution data must be provided when queuing an execution.") + now = datetime.now(tz=timezone.utc) data = { **({"executionStatus": status}), @@ -214,6 +253,7 @@ async def update_execution_status( **({"startedTime": now} if status == ExecutionStatus.RUNNING else {}), **({"endedTime": now} if status == ExecutionStatus.FAILED else {}), **({"endedTime": now} if status == ExecutionStatus.COMPLETED else {}), + **({"executionData": json.dumps(execution_data)} if execution_data else {}), } res = await AgentNodeExecution.prisma().update( @@ -239,32 +279,15 @@ async def get_execution_results(graph_exec_id: str) -> list[ExecutionResult]: executions = await AgentNodeExecution.prisma().find_many( where={"agentGraphExecutionId": graph_exec_id}, include=EXECUTION_RESULT_INCLUDE, # type: ignore - order={"addedTime": "asc"}, + order=[ + {"queuedTime": "asc"}, + {"addedTime": "asc"}, # Fallback: Incomplete execs has no queuedTime. + ], ) res = [ExecutionResult.from_db(execution) for execution in executions] return res -async def get_node_execution_input(node_exec_id: str) -> BlockInput: - """ - Get execution node input data from the previous node execution result. - - Returns: - dictionary of input data, key is the input name, value is the input data. - """ - execution = await AgentNodeExecution.prisma().find_unique_or_raise( - where={"id": node_exec_id}, - include=EXECUTION_RESULT_INCLUDE, # type: ignore - ) - if not execution.AgentNode: - raise ValueError(f"Node {execution.agentNodeId} not found.") - - return { - input_data.name: json.loads(input_data.data) - for input_data in execution.Input or [] - } - - LIST_SPLIT = "_$_" DICT_SPLIT = "_#_" OBJC_SPLIT = "_@_" @@ -341,3 +364,33 @@ def merge_execution_input(data: BlockInput) -> BlockInput: setattr(data[name], index, value) return data + + +async def get_latest_execution(node_id: str, graph_eid: str) -> ExecutionResult | None: + execution = await AgentNodeExecution.prisma().find_first( + where={ # type: ignore + "agentNodeId": node_id, + "agentGraphExecutionId": graph_eid, + "executionStatus": {"not": ExecutionStatus.INCOMPLETE}, + "executionData": {"not": None}, + }, + order={"queuedTime": "desc"}, + include=EXECUTION_RESULT_INCLUDE, # type: ignore + ) + if not execution: + return None + return ExecutionResult.from_db(execution) + + +async def get_incomplete_executions( + node_id: str, graph_eid: str +) -> list[ExecutionResult]: + executions = await AgentNodeExecution.prisma().find_many( + where={ # type: ignore + "agentNodeId": node_id, + "agentGraphExecutionId": graph_eid, + "executionStatus": ExecutionStatus.INCOMPLETE, + }, + include=EXECUTION_RESULT_INCLUDE, # type: ignore + ) + return [ExecutionResult.from_db(execution) for execution in executions] diff --git a/rnd/autogpt_server/autogpt_server/data/graph.py b/rnd/autogpt_server/autogpt_server/data/graph.py index 763a2dec169f..fd9ab0c1a301 100644 --- a/rnd/autogpt_server/autogpt_server/data/graph.py +++ b/rnd/autogpt_server/autogpt_server/data/graph.py @@ -17,6 +17,7 @@ class Link(BaseDbModel): sink_id: str source_name: str sink_name: str + is_static: bool = False @staticmethod def from_db(link: AgentNodeLink): @@ -26,6 +27,7 @@ def from_db(link: AgentNodeLink): source_id=link.agentNodeSourceId, sink_name=link.sinkName, sink_id=link.agentNodeSinkId, + is_static=link.isStatic, ) def __hash__(self): @@ -224,7 +226,6 @@ async def create_graph(graph: Graph) -> Graph: } ) - # TODO: replace bulk creation using create_many await asyncio.gather( *[ AgentNode.prisma().create( @@ -250,6 +251,7 @@ async def create_graph(graph: Graph) -> Graph: "sinkName": link.sink_name, "agentNodeSourceId": link.source_id, "agentNodeSinkId": link.sink_id, + "isStatic": link.is_static, } ) for link in graph.links diff --git a/rnd/autogpt_server/autogpt_server/executor/manager.py b/rnd/autogpt_server/autogpt_server/executor/manager.py index 731297d087b5..ecfef6fe9c2c 100644 --- a/rnd/autogpt_server/autogpt_server/executor/manager.py +++ b/rnd/autogpt_server/autogpt_server/executor/manager.py @@ -1,6 +1,7 @@ import asyncio import logging -from concurrent.futures import ProcessPoolExecutor +from concurrent.futures import Future, ProcessPoolExecutor, TimeoutError +from contextlib import contextmanager from typing import TYPE_CHECKING, Any, Coroutine, Generator, TypeVar if TYPE_CHECKING: @@ -8,11 +9,14 @@ from autogpt_server.data import db from autogpt_server.data.block import Block, BlockData, BlockInput, get_block -from autogpt_server.data.execution import ExecutionQueue, ExecutionStatus -from autogpt_server.data.execution import NodeExecution as Execution from autogpt_server.data.execution import ( + ExecutionQueue, + ExecutionStatus, + GraphExecution, + NodeExecution, create_graph_execution, - get_node_execution_input, + get_incomplete_executions, + get_latest_execution, merge_execution_input, parse_execution_output, update_execution_status, @@ -21,20 +25,21 @@ ) from autogpt_server.data.graph import Graph, Link, Node, get_graph, get_node from autogpt_server.util.service import AppService, expose, get_service_client +from autogpt_server.util.settings import Config logger = logging.getLogger(__name__) def get_log_prefix(graph_eid: str, node_eid: str, block_name: str = "-"): - return f"[ExecutionManager] [graph-{graph_eid}|node-{node_eid}|{block_name}]" + return f"[ExecutionManager][graph-eid-{graph_eid}|node-eid-{node_eid}|{block_name}]" T = TypeVar("T") -ExecutionStream = Generator[Execution, None, None] +ExecutionStream = Generator[NodeExecution, None, None] def execute_node( - loop: asyncio.AbstractEventLoop, api_client: "AgentServer", data: Execution + loop: asyncio.AbstractEventLoop, api_client: "AgentServer", data: NodeExecution ) -> ExecutionStream: """ Execute a node in the graph. This will trigger a block execution on a node, @@ -58,9 +63,8 @@ def wait(f: Coroutine[T, Any, T]) -> T: return loop.run_until_complete(f) def update_execution(status: ExecutionStatus): - api_client.send_execution_update( - wait(update_execution_status(node_exec_id, status)).model_dump() - ) + exec_update = wait(update_execution_status(node_exec_id, status)) + api_client.send_execution_update(exec_update.model_dump()) node = wait(get_node(node_id)) if not node: @@ -89,7 +93,7 @@ def update_execution(status: ExecutionStatus): wait(upsert_execution_output(node_exec_id, output_name, output_data)) update_execution(ExecutionStatus.COMPLETED) - for execution in enqueue_next_nodes( + for execution in _enqueue_next_nodes( api_client=api_client, loop=loop, node=node, @@ -107,67 +111,129 @@ def update_execution(status: ExecutionStatus): raise e -def enqueue_next_nodes( +@contextmanager +def synchronized(api_client: "AgentServer", key: Any): + api_client.acquire_lock(key) + try: + yield + finally: + api_client.release_lock(key) + + +def _enqueue_next_nodes( api_client: "AgentServer", loop: asyncio.AbstractEventLoop, node: Node, output: BlockData, graph_exec_id: str, prefix: str, -) -> list[Execution]: +) -> list[NodeExecution]: def wait(f: Coroutine[T, Any, T]) -> T: return loop.run_until_complete(f) - def execution_update(node_exec_id: str, status: ExecutionStatus): - api_client.send_execution_update( - wait(update_execution_status(node_exec_id, status)).model_dump() + def add_enqueued_execution( + node_exec_id: str, node_id: str, data: BlockInput + ) -> NodeExecution: + exec_update = wait( + update_execution_status(node_exec_id, ExecutionStatus.QUEUED, data) + ) + api_client.send_execution_update(exec_update.model_dump()) + return NodeExecution( + graph_exec_id=graph_exec_id, + node_exec_id=node_exec_id, + node_id=node_id, + data=data, ) - def update_execution_result(node_link: Link) -> Execution | None: + def register_next_executions(node_link: Link) -> list[NodeExecution]: + enqueued_executions = [] next_output_name = node_link.source_name next_input_name = node_link.sink_name next_node_id = node_link.sink_id next_data = parse_execution_output(output, next_output_name) if next_data is None: - return + return enqueued_executions next_node = wait(get_node(next_node_id)) if not next_node: logger.error(f"{prefix} Error, next node {next_node_id} not found.") - return - - next_node_exec_id = wait( - upsert_execution_input( - node_id=next_node_id, - graph_exec_id=graph_exec_id, - input_name=next_input_name, - data=next_data, + return enqueued_executions + + # Upserting execution input includes reading the existing input pins in the node + # which then either updating the existing execution input or creating a new one. + # While reading, we should avoid any other process to add input to the same node. + with synchronized(api_client, ("upsert_input", next_node_id, graph_exec_id)): + next_node_exec_id, next_node_input = wait( + upsert_execution_input( + node_id=next_node_id, + graph_exec_id=graph_exec_id, + input_name=next_input_name, + input_data=next_data, + ) ) - ) - next_node_input = wait(get_node_execution_input(next_node_exec_id)) + # Complete missing static input pins data using the last execution input. + static_link_names = { + link.sink_name + for link in next_node.input_links + if link.is_static and link.sink_name not in next_node_input + } + if static_link_names and ( + latest_execution := wait(get_latest_execution(next_node_id, graph_exec_id)) + ): + for name in static_link_names: + next_node_input[name] = latest_execution.input_data.get(name) + next_node_input, validation_msg = validate_exec(next_node, next_node_input) - suffix = f"{next_output_name}~{next_input_name}#{next_node_id}:{validation_msg}" + suffix = ( + f"{next_output_name}>{next_input_name}~{next_node_exec_id}:{validation_msg}" + ) if not next_node_input: logger.warning(f"{prefix} Skipped queueing {suffix}") - return + return enqueued_executions # Input is complete, enqueue the execution. logger.warning(f"{prefix} Enqueued {suffix}") - execution_update(next_node_exec_id, ExecutionStatus.QUEUED) - return Execution( - graph_exec_id=graph_exec_id, - node_exec_id=next_node_exec_id, - node_id=next_node.id, - data=next_node_input, + enqueued_executions.append( + add_enqueued_execution(next_node_exec_id, next_node_id, next_node_input) ) + if not node_link.is_static: + return enqueued_executions + + # If link is static, there could be some incomplete executions waiting for it. + # Load and complete the input missing input data, and try to re-enqueue them. + # While reading, we should avoid any other process to re-enqueue the same node. + with synchronized(api_client, ("upsert_input", next_node_id, graph_exec_id)): + for iexec in wait(get_incomplete_executions(next_node_id, graph_exec_id)): + idata = iexec.input_data + ineid = iexec.node_exec_id + + static_link_names = { + link.sink_name + for link in next_node.input_links + if link.is_static and link.sink_name not in idata + } + for input_name in static_link_names: + idata[input_name] = next_node_input[input_name] + + idata, msg = validate_exec(next_node, idata) + suffix = f"{next_output_name}>{next_input_name}~{ineid}:{msg}" + if not idata: + logger.warning(f"{prefix} Re-enqueueing skipped: {suffix}") + continue + logger.warning(f"{prefix} Re-enqueued {suffix}") + enqueued_executions.append( + add_enqueued_execution(iexec.node_exec_id, next_node_id, idata) + ) + return enqueued_executions + return [ execution for link in node.output_links - if (execution := update_execution_result(link)) + for execution in register_next_executions(link) ] @@ -228,44 +294,118 @@ def get_agent_server_client() -> "AgentServer": class Executor: - loop: asyncio.AbstractEventLoop + """ + This class contains event handlers for the process pool executor events. + + The main events are: + on_node_executor_start: Initialize the process that executes the node. + on_node_execution: Execution logic for a node. + + on_graph_executor_start: Initialize the process that executes the graph. + on_graph_execution: Execution logic for a graph. + + The execution flow: + 1. Graph execution request is added to the queue. + 2. Graph executor loop picks the request from the queue. + 3. Graph executor loop submits the graph execution request to the executor pool. + [on_graph_execution] + 4. Graph executor initialize the node execution queue. + 5. Graph executor adds the starting nodes to the node execution queue. + 6. Graph executor waits for all nodes to be executed. + [on_node_execution] + 7. Node executor picks the node execution request from the queue. + 8. Node executor executes the node. + 9. Node executor enqueues the next executed nodes to the node execution queue. + """ @classmethod - def on_executor_start(cls): + def on_node_executor_start(cls): cls.loop = asyncio.new_event_loop() cls.loop.run_until_complete(db.connect()) cls.agent_server_client = get_agent_server_client() @classmethod - def on_start_execution(cls, q: ExecutionQueue, data: Execution) -> bool: + def on_node_execution(cls, q: ExecutionQueue[NodeExecution], data: NodeExecution): prefix = get_log_prefix(data.graph_exec_id, data.node_exec_id) try: - logger.warning(f"{prefix} Start execution") + logger.warning(f"{prefix} Start node execution") for execution in execute_node(cls.loop, cls.agent_server_client, data): q.add(execution) - return True + logger.warning(f"{prefix} Finished node execution") except Exception as e: - logger.exception(f"{prefix} Error: {e}") - return False + logger.exception(f"{prefix} Failed node execution: {e}") + + @classmethod + def on_graph_executor_start(cls): + cls.pool_size = Config().num_node_workers + cls.executor = ProcessPoolExecutor( + max_workers=cls.pool_size, + initializer=cls.on_node_executor_start, + ) + logger.warning(f"Graph executor started with max-{cls.pool_size} node workers.") + + @classmethod + def on_graph_execution(cls, graph_data: GraphExecution): + prefix = get_log_prefix(graph_data.graph_exec_id, "*") + logger.warning(f"{prefix} Start graph execution") + + try: + queue = ExecutionQueue[NodeExecution]() + for node_exec in graph_data.start_node_execs: + queue.add(node_exec) + + futures: dict[str, Future] = {} + while not queue.empty(): + execution = queue.get() + + # Avoid parallel execution of the same node. + fut = futures.get(execution.node_id) + if fut and not fut.done(): + cls.wait_future(fut) + logger.warning(f"{prefix} Re-enqueueing {execution.node_id}") + queue.add(execution) + continue + + futures[execution.node_id] = cls.executor.submit( + cls.on_node_execution, queue, execution + ) + + # Avoid terminating graph execution when some nodes are still running. + while queue.empty() and futures: + for node_id, future in list(futures.items()): + if future.done(): + del futures[node_id] + elif queue.empty(): + cls.wait_future(future) + + logger.warning(f"{prefix} Finished graph execution") + except Exception as e: + logger.exception(f"{prefix} Failed graph execution: {e}") + + @classmethod + def wait_future(cls, future: Future): + try: + future.result(timeout=3) + except TimeoutError: + # Avoid being blocked by long-running node, by not waiting its completion. + pass class ExecutionManager(AppService): - def __init__(self, pool_size: int): - self.pool_size = pool_size - self.queue = ExecutionQueue() + def __init__(self): + self.pool_size = Config().num_graph_workers + self.queue = ExecutionQueue[GraphExecution]() def run_service(self): with ProcessPoolExecutor( max_workers=self.pool_size, - initializer=Executor.on_executor_start, + initializer=Executor.on_graph_executor_start, ) as executor: - logger.warning(f"Execution manager started with {self.pool_size} workers.") + logger.warning( + f"Execution manager started with max-{self.pool_size} graph workers." + ) while True: - executor.submit( - Executor.on_start_execution, - self.queue, - self.queue.get(), - ) + executor.submit(Executor.on_graph_execution, self.queue.get()) @property def agent_server_client(self) -> "AgentServer": @@ -292,32 +432,28 @@ def add_execution(self, graph_id: str, data: BlockInput) -> dict[Any, Any]: nodes_input=nodes_input, ) ) - executions: list[BlockInput] = [] + + starting_node_execs = [] for node_exec in node_execs: - self.add_node_execution( - Execution( + starting_node_execs.append( + NodeExecution( graph_exec_id=node_exec.graph_exec_id, node_exec_id=node_exec.node_exec_id, node_id=node_exec.node_id, data=node_exec.input_data, ) ) - - executions.append( - { - "id": node_exec.node_exec_id, - "node_id": node_exec.node_id, - } + exec_update = self.run_and_wait( + update_execution_status( + node_exec.node_exec_id, ExecutionStatus.QUEUED, node_exec.input_data + ) ) + self.agent_server_client.send_execution_update(exec_update.model_dump()) - return { - "id": graph_exec_id, - "executions": executions, - } - - def add_node_execution(self, execution: Execution) -> Execution: - res = self.run_and_wait( - update_execution_status(execution.node_exec_id, ExecutionStatus.QUEUED) + graph_exec = GraphExecution( + graph_exec_id=graph_exec_id, + start_node_execs=starting_node_execs, ) - self.agent_server_client.send_execution_update(res.model_dump()) - return self.queue.add(execution) + self.queue.add(graph_exec) + + return {"id": graph_exec_id} diff --git a/rnd/autogpt_server/autogpt_server/server/server.py b/rnd/autogpt_server/autogpt_server/server/server.py index 3d35af8812b3..6d90a24500ac 100644 --- a/rnd/autogpt_server/autogpt_server/server/server.py +++ b/rnd/autogpt_server/autogpt_server/server/server.py @@ -18,9 +18,14 @@ from fastapi.staticfiles import StaticFiles import autogpt_server.server.ws_api -from autogpt_server.data import block, db, execution +from autogpt_server.data import block, db from autogpt_server.data import graph as graph_db from autogpt_server.data.block import BlockInput, CompletedBlockOutput +from autogpt_server.data.execution import ( + ExecutionResult, + get_execution_results, + list_executions, +) from autogpt_server.executor import ExecutionManager, ExecutionScheduler from autogpt_server.server.conn_manager import ConnectionManager from autogpt_server.server.model import ( @@ -30,17 +35,19 @@ WsMessage, ) from autogpt_server.util.data import get_frontend_path +from autogpt_server.util.lock import KeyedMutex from autogpt_server.util.service import AppService, expose, get_service_client from autogpt_server.util.settings import Settings class AgentServer(AppService): - event_queue: asyncio.Queue[execution.ExecutionResult] = asyncio.Queue() + event_queue: asyncio.Queue[ExecutionResult] = asyncio.Queue() manager = ConnectionManager() + mutex = KeyedMutex() async def event_broadcaster(self): while True: - event: execution.ExecutionResult = await self.event_queue.get() + event: ExecutionResult = await self.event_queue.get() await self.manager.send_execution_result(event) @asynccontextmanager @@ -462,9 +469,6 @@ async def create_graph( status_code=400, detail="Either graph or template_id must be provided." ) - # TODO: replace uuid generation here to DB generated uuids. - graph.id = str(uuid.uuid4()) - graph.is_template = is_template graph.is_active = not is_template @@ -552,17 +556,17 @@ async def list_graph_runs( status_code=404, detail=f"Agent #{graph_id}{rev} not found." ) - return await execution.list_executions(graph_id, graph_version) + return await list_executions(graph_id, graph_version) @classmethod async def get_run_execution_results( cls, graph_id: str, run_id: str - ) -> list[execution.ExecutionResult]: + ) -> list[ExecutionResult]: graph = await graph_db.get_graph(graph_id) if not graph: raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.") - return await execution.get_execution_results(run_id) + return await get_execution_results(run_id) async def create_schedule( self, graph_id: str, cron: str, input_data: dict[Any, Any] @@ -591,14 +595,32 @@ def get_execution_schedules(self, graph_id: str) -> dict[str, str]: @expose def send_execution_update(self, execution_result_dict: dict[Any, Any]): - execution_result = execution.ExecutionResult(**execution_result_dict) + execution_result = ExecutionResult(**execution_result_dict) self.run_and_wait(self.event_queue.put(execution_result)) + @expose + def acquire_lock(self, key: Any): + self.mutex.lock(key) + + @expose + def release_lock(self, key: Any): + self.mutex.unlock(key) + @classmethod def update_configuration( cls, updated_settings: Annotated[ - Dict[str, Any], Body(examples=[{"config": {"num_workers": 10}}]) + Dict[str, Any], + Body( + examples=[ + { + "config": { + "num_graph_workers": 10, + "num_node_workers": 10, + } + } + ] + ), ], ): settings = Settings() diff --git a/rnd/autogpt_server/autogpt_server/util/lock.py b/rnd/autogpt_server/autogpt_server/util/lock.py new file mode 100644 index 000000000000..6c66b67a3079 --- /dev/null +++ b/rnd/autogpt_server/autogpt_server/util/lock.py @@ -0,0 +1,32 @@ +from threading import Lock +from typing import Any + +from expiringdict import ExpiringDict + + +class KeyedMutex: + """ + This class provides a mutex that can be locked and unlocked by a specific key. + It uses an ExpiringDict to automatically clear the mutex after a specified timeout, + in case the key is not unlocked for a specified duration, to prevent memory leaks. + """ + + def __init__(self): + self.locks: dict[Any, Lock] = ExpiringDict(max_len=6000, max_age_seconds=60) + self.locks_lock = Lock() + + def lock(self, key: Any): + with self.locks_lock: + if key not in self.locks: + self.locks[key] = (lock := Lock()) + else: + lock = self.locks[key] + lock.acquire() + + def unlock(self, key: Any): + with self.locks_lock: + if key in self.locks: + lock = self.locks.pop(key) + else: + return + lock.release() diff --git a/rnd/autogpt_server/autogpt_server/util/service.py b/rnd/autogpt_server/autogpt_server/util/service.py index ff55135a6a96..5719a8e54258 100644 --- a/rnd/autogpt_server/autogpt_server/util/service.py +++ b/rnd/autogpt_server/autogpt_server/util/service.py @@ -51,11 +51,11 @@ def run_service(self): while True: time.sleep(10) - def run_async(self, coro: Coroutine[T, Any, T]): + def __run_async(self, coro: Coroutine[T, Any, T]): return asyncio.run_coroutine_threadsafe(coro, self.shared_event_loop) def run_and_wait(self, coro: Coroutine[T, Any, T]) -> T: - future = self.run_async(coro) + future = self.__run_async(coro) return future.result() def run(self): @@ -85,7 +85,6 @@ def __start_pyro(self): daemon.requestLoop() def __start_async_loop(self): - # asyncio.set_event_loop(self.shared_event_loop) self.shared_event_loop.run_forever() diff --git a/rnd/autogpt_server/autogpt_server/util/settings.py b/rnd/autogpt_server/autogpt_server/util/settings.py index b320051f9a24..a309b04357e9 100644 --- a/rnd/autogpt_server/autogpt_server/util/settings.py +++ b/rnd/autogpt_server/autogpt_server/util/settings.py @@ -41,8 +41,17 @@ def updated_fields(self): class Config(UpdateTrackingModel["Config"], BaseSettings): """Config for the server.""" - num_workers: int = Field( - default=9, ge=1, le=100, description="Number of workers to use for execution." + num_graph_workers: int = Field( + default=1, + ge=1, + le=100, + description="Maximum number of workers to use for graph execution.", + ) + num_node_workers: int = Field( + default=1, + ge=1, + le=100, + description="Maximum number of workers to use for node execution within a single graph.", ) # Add more configuration fields as needed diff --git a/rnd/autogpt_server/autogpt_server/util/test.py b/rnd/autogpt_server/autogpt_server/util/test.py index 00a59a4007c4..8242e776dd5a 100644 --- a/rnd/autogpt_server/autogpt_server/util/test.py +++ b/rnd/autogpt_server/autogpt_server/util/test.py @@ -13,7 +13,7 @@ class SpinTestServer: def __init__(self): self.name_server = PyroNameServer() - self.exec_manager = ExecutionManager(1) + self.exec_manager = ExecutionManager() self.agent_server = AgentServer() self.scheduler = ExecutionScheduler() diff --git a/rnd/autogpt_server/config.default.json b/rnd/autogpt_server/config.default.json index cbc8006a2477..b3afd56b5dc5 100644 --- a/rnd/autogpt_server/config.default.json +++ b/rnd/autogpt_server/config.default.json @@ -1,3 +1,4 @@ { - "num_workers": 5 -} \ No newline at end of file + "num_graph_workers": 10, + "num_node_workers": 10 +} diff --git a/rnd/autogpt_server/migrations/20240719161138_init/migration.sql b/rnd/autogpt_server/migrations/20240719161138_init/migration.sql deleted file mode 100644 index c23105bc0a0f..000000000000 --- a/rnd/autogpt_server/migrations/20240719161138_init/migration.sql +++ /dev/null @@ -1,128 +0,0 @@ --- CreateTable -CREATE TABLE "AgentGraph" ( - "id" TEXT NOT NULL, - "version" INTEGER NOT NULL DEFAULT 1, - "name" TEXT, - "description" TEXT, - "isActive" BOOLEAN NOT NULL DEFAULT true, - "isTemplate" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "AgentGraph_pkey" PRIMARY KEY ("id","version") -); - --- CreateTable -CREATE TABLE "AgentNode" ( - "id" TEXT NOT NULL, - "agentBlockId" TEXT NOT NULL, - "agentGraphId" TEXT NOT NULL, - "agentGraphVersion" INTEGER NOT NULL DEFAULT 1, - "constantInput" TEXT NOT NULL DEFAULT '{}', - "metadata" TEXT NOT NULL DEFAULT '{}', - - CONSTRAINT "AgentNode_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentNodeLink" ( - "id" TEXT NOT NULL, - "agentNodeSourceId" TEXT NOT NULL, - "sourceName" TEXT NOT NULL, - "agentNodeSinkId" TEXT NOT NULL, - "sinkName" TEXT NOT NULL, - - CONSTRAINT "AgentNodeLink_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentBlock" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "inputSchema" TEXT NOT NULL, - "outputSchema" TEXT NOT NULL, - - CONSTRAINT "AgentBlock_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentGraphExecution" ( - "id" TEXT NOT NULL, - "agentGraphId" TEXT NOT NULL, - "agentGraphVersion" INTEGER NOT NULL DEFAULT 1, - - CONSTRAINT "AgentGraphExecution_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentNodeExecution" ( - "id" TEXT NOT NULL, - "agentGraphExecutionId" TEXT NOT NULL, - "agentNodeId" TEXT NOT NULL, - "executionStatus" TEXT NOT NULL, - "addedTime" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "queuedTime" TIMESTAMP(3), - "startedTime" TIMESTAMP(3), - "endedTime" TIMESTAMP(3), - - CONSTRAINT "AgentNodeExecution_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentNodeExecutionInputOutput" ( - "id" TEXT NOT NULL, - "name" TEXT NOT NULL, - "data" TEXT NOT NULL, - "time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "referencedByInputExecId" TEXT, - "referencedByOutputExecId" TEXT, - - CONSTRAINT "AgentNodeExecutionInputOutput_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AgentGraphExecutionSchedule" ( - "id" TEXT NOT NULL, - "agentGraphId" TEXT NOT NULL, - "agentGraphVersion" INTEGER NOT NULL DEFAULT 1, - "schedule" TEXT NOT NULL, - "isEnabled" BOOLEAN NOT NULL DEFAULT true, - "inputData" TEXT NOT NULL, - "lastUpdated" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "AgentGraphExecutionSchedule_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "AgentBlock_name_key" ON "AgentBlock"("name"); - --- CreateIndex -CREATE INDEX "AgentGraphExecutionSchedule_isEnabled_idx" ON "AgentGraphExecutionSchedule"("isEnabled"); - --- AddForeignKey -ALTER TABLE "AgentNode" ADD CONSTRAINT "AgentNode_agentBlockId_fkey" FOREIGN KEY ("agentBlockId") REFERENCES "AgentBlock"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNode" ADD CONSTRAINT "AgentNode_agentGraphId_agentGraphVersion_fkey" FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeLink" ADD CONSTRAINT "AgentNodeLink_agentNodeSourceId_fkey" FOREIGN KEY ("agentNodeSourceId") REFERENCES "AgentNode"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeLink" ADD CONSTRAINT "AgentNodeLink_agentNodeSinkId_fkey" FOREIGN KEY ("agentNodeSinkId") REFERENCES "AgentNode"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentGraphExecution" ADD CONSTRAINT "AgentGraphExecution_agentGraphId_agentGraphVersion_fkey" FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeExecution" ADD CONSTRAINT "AgentNodeExecution_agentGraphExecutionId_fkey" FOREIGN KEY ("agentGraphExecutionId") REFERENCES "AgentGraphExecution"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeExecution" ADD CONSTRAINT "AgentNodeExecution_agentNodeId_fkey" FOREIGN KEY ("agentNodeId") REFERENCES "AgentNode"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeExecutionInputOutput" ADD CONSTRAINT "AgentNodeExecutionInputOutput_referencedByInputExecId_fkey" FOREIGN KEY ("referencedByInputExecId") REFERENCES "AgentNodeExecution"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentNodeExecutionInputOutput" ADD CONSTRAINT "AgentNodeExecutionInputOutput_referencedByOutputExecId_fkey" FOREIGN KEY ("referencedByOutputExecId") REFERENCES "AgentNodeExecution"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "AgentGraphExecutionSchedule" ADD CONSTRAINT "AgentGraphExecutionSchedule_agentGraphId_agentGraphVersion_fkey" FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/rnd/autogpt_server/migrations/20240724150133_node_input_unique_constraint/migration.sql b/rnd/autogpt_server/migrations/20240724150133_node_input_unique_constraint/migration.sql new file mode 100644 index 000000000000..89e1567c29f6 --- /dev/null +++ b/rnd/autogpt_server/migrations/20240724150133_node_input_unique_constraint/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[referencedByInputExecId,referencedByOutputExecId,name]` on the table `AgentNodeExecutionInputOutput` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "AgentNodeExecutionInputOutput_referencedByInputExecId_referencedByOutputExecId_name_key" ON "AgentNodeExecutionInputOutput"("referencedByInputExecId", "referencedByOutputExecId", "name"); diff --git a/rnd/autogpt_server/migrations/20240729051155_static_input_link/migration.sql b/rnd/autogpt_server/migrations/20240729051155_static_input_link/migration.sql new file mode 100644 index 000000000000..fa0ac70dc9a2 --- /dev/null +++ b/rnd/autogpt_server/migrations/20240729051155_static_input_link/migration.sql @@ -0,0 +1,20 @@ +-- AlterTable +ALTER TABLE "AgentNodeExecution" ADD COLUMN "executionData" TEXT; + +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_AgentNodeLink" ( + "id" TEXT NOT NULL PRIMARY KEY, + "agentNodeSourceId" TEXT NOT NULL, + "sourceName" TEXT NOT NULL, + "agentNodeSinkId" TEXT NOT NULL, + "sinkName" TEXT NOT NULL, + "isStatic" BOOLEAN NOT NULL DEFAULT false, + CONSTRAINT "AgentNodeLink_agentNodeSourceId_fkey" FOREIGN KEY ("agentNodeSourceId") REFERENCES "AgentNode" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "AgentNodeLink_agentNodeSinkId_fkey" FOREIGN KEY ("agentNodeSinkId") REFERENCES "AgentNode" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_AgentNodeLink" ("agentNodeSinkId", "agentNodeSourceId", "id", "sinkName", "sourceName") SELECT "agentNodeSinkId", "agentNodeSourceId", "id", "sinkName", "sourceName" FROM "AgentNodeLink"; +DROP TABLE "AgentNodeLink"; +ALTER TABLE "new_AgentNodeLink" RENAME TO "AgentNodeLink"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/rnd/autogpt_server/migrations/migration_lock.toml b/rnd/autogpt_server/migrations/migration_lock.toml index 6fcf33dafba6..e5e5c4705ab0 100644 --- a/rnd/autogpt_server/migrations/migration_lock.toml +++ b/rnd/autogpt_server/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "sqlite" +provider = "sqlite" \ No newline at end of file diff --git a/rnd/autogpt_server/poetry.lock b/rnd/autogpt_server/poetry.lock index 8f1e9349b7fe..92818096b039 100644 --- a/rnd/autogpt_server/poetry.lock +++ b/rnd/autogpt_server/poetry.lock @@ -25,7 +25,7 @@ requests = "*" sentry-sdk = "^1.40.4" [package.extras] -benchmark = ["agbenchmark @ file:///workspaces/AutoGPT/benchmark"] +benchmark = ["agbenchmark"] [package.source] type = "directory" @@ -329,7 +329,7 @@ watchdog = "4.0.0" webdriver-manager = "^4.0.1" [package.extras] -benchmark = ["agbenchmark @ file:///workspaces/AutoGPT/benchmark"] +benchmark = ["agbenchmark"] [package.source] type = "directory" @@ -501,17 +501,17 @@ numpy = {version = ">=1.19.0", markers = "python_version >= \"3.9\""} [[package]] name = "boto3" -version = "1.34.146" +version = "1.34.147" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.146-py3-none-any.whl", hash = "sha256:7ec568fb19bce82a70be51f08fddac1ef927ca3fb0896cbb34303a012ba228d8"}, - {file = "boto3-1.34.146.tar.gz", hash = "sha256:5686fe2a6d1aa1de8a88e9589cdcc33361640d3d7a13da718a30717248886124"}, + {file = "boto3-1.34.147-py3-none-any.whl", hash = "sha256:e1cef9a1a301866bcdee32ae0c699465eb2345f9a8e613a5835821430165ff6d"}, + {file = "boto3-1.34.147.tar.gz", hash = "sha256:9ec1c6ab22588242a47549f51a63dfc7c21fdf95a94820fc6e629ab060c38bd9"}, ] [package.dependencies] -botocore = ">=1.34.146,<1.35.0" +botocore = ">=1.34.147,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -520,13 +520,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.146" +version = "1.34.147" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.146-py3-none-any.whl", hash = "sha256:3fd4782362bd29c192704ebf859c5c8c5189ad05719e391eefe23088434427ae"}, - {file = "botocore-1.34.146.tar.gz", hash = "sha256:849cb8e54e042443aeabcd7822b5f2b76cb5cfe33fe3a71f91c7c069748a869c"}, + {file = "botocore-1.34.147-py3-none-any.whl", hash = "sha256:be94a2f4874b1d1705cae2bd512c475047497379651678593acb6c61c50d91de"}, + {file = "botocore-1.34.147.tar.gz", hash = "sha256:2e8f000b77e4ca345146cb2edab6403769a517b564f627bb084ab335417f3dbe"}, ] [package.dependencies] @@ -1179,6 +1179,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "expiringdict" +version = "1.2.2" +description = "Dictionary with auto-expiring values for caching purposes" +optional = false +python-versions = "*" +files = [ + {file = "expiringdict-1.2.2-py3-none-any.whl", hash = "sha256:09a5d20bc361163e6432a874edd3179676e935eb81b925eccef48d409a8a45e8"}, + {file = "expiringdict-1.2.2.tar.gz", hash = "sha256:300fb92a7e98f15b05cf9a856c1415b3bc4f2e132be07daa326da6414c23ee09"}, +] + +[package.extras] +tests = ["coverage", "coveralls", "dill", "mock", "nose"] + [[package]] name = "fastapi" version = "0.109.2" @@ -2057,13 +2071,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "huggingface-hub" -version = "0.24.0" +version = "0.24.1" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.24.0-py3-none-any.whl", hash = "sha256:7ad92edefb93d8145c061f6df8d99df2ff85f8379ba5fac8a95aca0642afa5d7"}, - {file = "huggingface_hub-0.24.0.tar.gz", hash = "sha256:6c7092736b577d89d57b3cdfea026f1b0dc2234ae783fa0d59caf1bf7d52dfa7"}, + {file = "huggingface_hub-0.24.1-py3-none-any.whl", hash = "sha256:d3a623d0f2cbb9399299aefc85e3423fa2689f18ab9b6e1aa0f95d1793889f30"}, + {file = "huggingface_hub-0.24.1.tar.gz", hash = "sha256:6915e34c7b1282b0f7c2387c12db21003b79889f1dad57da0434ecd10f3293a8"}, ] [package.dependencies] @@ -2369,9 +2383,6 @@ files = [ {file = "lief-0.14.1-cp312-cp312-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:497b88f9c9aaae999766ba188744ee35c5f38b4b64016f7dbb7037e9bf325382"}, {file = "lief-0.14.1-cp312-cp312-win32.whl", hash = "sha256:08bad88083f696915f8dcda4042a3bfc514e17462924ec8984085838b2261921"}, {file = "lief-0.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:e131d6158a085f8a72124136816fefc29405c725cd3695ce22a904e471f0f815"}, - {file = "lief-0.14.1-cp313-cp313-manylinux_2_28_x86_64.manylinux_2_27_x86_64.whl", hash = "sha256:f9ff9a6959fb6d0e553cca41cd1027b609d27c5073e98d9fad8b774fbb5746c2"}, - {file = "lief-0.14.1-cp313-cp313-win32.whl", hash = "sha256:95f295a7cc68f4e14ce7ea4ff8082a04f5313c2e5e63cc2bbe9d059190b7e4d5"}, - {file = "lief-0.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:cdc1123c2e27970f8c8353505fd578e634ab33193c8d1dff36dc159e25599a40"}, {file = "lief-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df650fa05ca131e4dfeb42c77985e1eb239730af9944bc0aadb1dfac8576e0e8"}, {file = "lief-0.14.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:b4e76eeb48ca2925c6ca6034d408582615f2faa855f9bb11482e7acbdecc4803"}, {file = "lief-0.14.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:016e4fac91303466024154dd3c4b599e8b7c52882f72038b62a2be386d98c8f9"}, @@ -2388,13 +2399,13 @@ files = [ [[package]] name = "litellm" -version = "1.41.27" +version = "1.41.28" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.41.27-py3-none-any.whl", hash = "sha256:df97e46517129826ae958e6571b22714e79f3eba76af2f0fd812f499b21b7d2c"}, - {file = "litellm-1.41.27.tar.gz", hash = "sha256:c70996f665be3c40177622afd9c54408a7430ede0e590abd8e9585f0285d6b87"}, + {file = "litellm-1.41.28-py3-none-any.whl", hash = "sha256:a1f0536abd873b58ddb0320736b355bac01b6d94a137a3963d83b6be05867d59"}, + {file = "litellm-1.41.28.tar.gz", hash = "sha256:30b30c1ee76d6a378a33395f5d4fccfe204be7c8b63f0a77b6d13bf72fc80942"}, ] [package.dependencies] @@ -4235,19 +4246,19 @@ files = [ [[package]] name = "pyreqwest-impersonate" -version = "0.5.2" +version = "0.5.3" description = "HTTP client that can impersonate web browsers, mimicking their headers and `TLS/JA3/JA4/HTTP2` fingerprints" optional = false python-versions = ">=3.8" files = [ - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ec8c9318190020bad3f12a62aedc5faaa92078c38cf48189e98cd692b82e7c28"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca7fc399a672558de1d50b5beb5a153c7002adbe332edd7a752f1d19c35d7f7f"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b453ab0a7f30a279151e88bd52449a01931307986d7c2a6a92a699d3ba58b66"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9c78ab908cc10a195ddbfc60c18b205237233f76b4f56c28ebc1afe80210335"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31db4e6e491868c227a252ef3a8fa8b7accc306e741934f74ad3e6fb096e334c"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3ce34743e3ce2656ba262a325103b96e1d4f301be5f3dd8cb21ee4eec4c6e1b7"}, - {file = "pyreqwest_impersonate-0.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:e72b39a64c12a35a1689af558d62c67f94d14310043e404a90550e39ddd4af7c"}, - {file = "pyreqwest_impersonate-0.5.2.tar.gz", hash = "sha256:50a57c4b139788606b311757ddf36efd82ea1c952decea808b4196220bd43803"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f15922496f728769fb9e1b116d5d9d7ba5525d0f2f7a76a41a1daef8b2e0c6c3"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:77533133ae73020e59bc56d776eea3fe3af4ac41d763a89f39c495436da0f4cf"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436055fa3eeb3e01e2e8efd42a9f6c4ab62fd643eddc7c66d0e671b71605f273"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d2e981a525fb72c1521f454e5581d2c7a3b1fcf1c97c0acfcb7a923d8cf3e"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a6bf986d4a165f6976b3e862111e2a46091883cb55e9e6325150f5aea2644229"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b7397f6dad3d5ae158e0b272cb3eafe8382e71775d829b286ae9c21cb5a879ff"}, + {file = "pyreqwest_impersonate-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:6026e4751b5912aec1e45238c07daf1e2c9126b3b32b33396b72885021e8990c"}, + {file = "pyreqwest_impersonate-0.5.3.tar.gz", hash = "sha256:f21c10609958ff5be18df0c329eed42d2b3ba8a339b65dc5f96ab74537231692"}, ] [package.extras] @@ -4255,13 +4266,13 @@ dev = ["pytest (>=8.1.1)"] [[package]] name = "pyright" -version = "1.1.372" +version = "1.1.373" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.372-py3-none-any.whl", hash = "sha256:25b15fb8967740f0949fd35b963777187f0a0404c0bd753cc966ec139f3eaa0b"}, - {file = "pyright-1.1.372.tar.gz", hash = "sha256:a9f5e0daa955daaa17e3d1ef76d3623e75f8afd5e37b437d3ff84d5b38c15420"}, + {file = "pyright-1.1.373-py3-none-any.whl", hash = "sha256:b805413227f2c209f27b14b55da27fe5e9fb84129c9f1eb27708a5d12f6f000e"}, + {file = "pyright-1.1.373.tar.gz", hash = "sha256:f41bcfc8b9d1802b09921a394d6ae1ce19694957b628bc657629688daf8a83ff"}, ] [package.dependencies] @@ -4831,41 +4842,22 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "selenium" -version = "4.22.0" -description = "Official Python bindings for Selenium WebDriver" -optional = false -python-versions = ">=3.8" -files = [ - {file = "selenium-4.22.0-py3-none-any.whl", hash = "sha256:e424991196e9857e19bf04fe5c1c0a4aac076794ff5e74615b1124e729d93104"}, - {file = "selenium-4.22.0.tar.gz", hash = "sha256:903c8c9d61b3eea6fcc9809dc7d9377e04e2ac87709876542cc8f863e482c4ce"}, -] - -[package.dependencies] -certifi = ">=2021.10.8" -trio = ">=0.17,<1.0" -trio-websocket = ">=0.9,<1.0" -typing_extensions = ">=4.9.0" -urllib3 = {version = ">=1.26,<3", extras = ["socks"]} -websocket-client = ">=1.8.0" - -[[package]] -name = "selenium" -version = "4.23.0" +version = "4.23.1" description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.23.0-py3-none-any.whl", hash = "sha256:3fa5124c8ba071a2d22f7512a80e7799f5a5492d5e20ada1909fe66a476b2a44"}, - {file = "selenium-4.23.0.tar.gz", hash = "sha256:88f36e3fe6d1d3a9e0626f527f4bd00f0300d43e93f51f59771a911078d4f472"}, + {file = "selenium-4.23.1-py3-none-any.whl", hash = "sha256:3a8d9f23dc636bd3840dd56f00c2739e32ec0c1e34a821dd553e15babef24477"}, + {file = "selenium-4.23.1.tar.gz", hash = "sha256:128d099e66284437e7128d2279176ec7a06e6ec7426e167f5d34987166bd8f46"}, ] [package.dependencies] certifi = ">=2021.10.8" trio = ">=0.17,<1.0" trio-websocket = ">=0.9,<1.0" -typing_extensions = ">=4.9.0,<4.10.0" +typing_extensions = ">=4.9,<5.0" urllib3 = {version = ">=1.26,<3", extras = ["socks"]} -websocket-client = "1.8.0" +websocket-client = ">=1.8,<2.0" [[package]] name = "sentry-sdk" @@ -5692,17 +5684,6 @@ rich = ">=10.11.0" shellingham = ">=1.3.0" typing-extensions = ">=3.7.4.3" -[[package]] -name = "typing-extensions" -version = "4.9.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -6381,4 +6362,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "422edccf59dc5cdcc1cb0f6991228b2f58ef807a934d1aeda9643fa0cad27780" +content-hash = "9013ff78344cb68878809bd7220453879c32c9291e39d99321dbcc9a7359855c" diff --git a/rnd/autogpt_server/postgres/migrations/20240726131311_node_input_unique_constraint/migration.sql b/rnd/autogpt_server/postgres/migrations/20240726131311_node_input_unique_constraint/migration.sql new file mode 100644 index 000000000000..284457cd5246 --- /dev/null +++ b/rnd/autogpt_server/postgres/migrations/20240726131311_node_input_unique_constraint/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[referencedByInputExecId,referencedByOutputExecId,name]` on the table `AgentNodeExecutionInputOutput` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "AgentNodeExecutionInputOutput_referencedByInputExecId_refer_key" ON "AgentNodeExecutionInputOutput"("referencedByInputExecId", "referencedByOutputExecId", "name"); diff --git a/rnd/autogpt_server/postgres/migrations/20240729061216_static_input_link/migration.sql b/rnd/autogpt_server/postgres/migrations/20240729061216_static_input_link/migration.sql new file mode 100644 index 000000000000..5e756d5215e5 --- /dev/null +++ b/rnd/autogpt_server/postgres/migrations/20240729061216_static_input_link/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "AgentNodeExecution" ADD COLUMN "executionData" TEXT; + +-- AlterTable +ALTER TABLE "AgentNodeLink" ADD COLUMN "isStatic" BOOLEAN NOT NULL DEFAULT false; diff --git a/rnd/autogpt_server/postgres/schema.prisma b/rnd/autogpt_server/postgres/schema.prisma index 1aabe7bbfbd4..9c36e220d022 100644 --- a/rnd/autogpt_server/postgres/schema.prisma +++ b/rnd/autogpt_server/postgres/schema.prisma @@ -65,6 +65,9 @@ model AgentNodeLink { agentNodeSinkId String AgentNodeSink AgentNode @relation("AgentNodeSink", fields: [agentNodeSinkId], references: [id]) sinkName String + + // Default: the data coming from the source can only be consumed by the sink once, Static: input data will be reused. + isStatic Boolean @default(false) } // This model describes a component that will be executed by the AgentNode. @@ -108,6 +111,8 @@ model AgentNodeExecution { // sqlite does not support enum // enum Status { INCOMPLETE, QUEUED, RUNNING, SUCCESS, FAILED } executionStatus String + // Final JSON serialized input data for the node execution. + executionData String? addedTime DateTime @default(now()) queuedTime DateTime? startedTime DateTime? @@ -127,6 +132,9 @@ model AgentNodeExecutionInputOutput { ReferencedByInputExec AgentNodeExecution? @relation("AgentNodeExecutionInput", fields: [referencedByInputExecId], references: [id]) referencedByOutputExecId String? ReferencedByOutputExec AgentNodeExecution? @relation("AgentNodeExecutionOutput", fields: [referencedByOutputExecId], references: [id]) + + // Input and Output pin names are unique for each AgentNodeExecution. + @@unique([referencedByInputExecId, referencedByOutputExecId, name]) } // This model describes the recurring execution schedule of an Agent. diff --git a/rnd/autogpt_server/pyproject.toml b/rnd/autogpt_server/pyproject.toml index 63f98d012b90..26f6f22cff05 100644 --- a/rnd/autogpt_server/pyproject.toml +++ b/rnd/autogpt_server/pyproject.toml @@ -38,6 +38,7 @@ youtube-transcript-api = "^0.6.2" ollama = "^0.3.0" feedparser = "^6.0.11" python-dotenv = "^1.0.1" +expiringdict = "^1.2.2" [tool.poetry.group.dev.dependencies] cx-freeze = { git = "https://github.com/ntindle/cx_Freeze.git", rev = "main", develop = true } diff --git a/rnd/autogpt_server/schema.prisma b/rnd/autogpt_server/schema.prisma index 1aabe7bbfbd4..ce5ab6ad94ee 100644 --- a/rnd/autogpt_server/schema.prisma +++ b/rnd/autogpt_server/schema.prisma @@ -1,6 +1,6 @@ datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "sqlite" + url = "file:./dev.db" } generator client { @@ -65,6 +65,9 @@ model AgentNodeLink { agentNodeSinkId String AgentNodeSink AgentNode @relation("AgentNodeSink", fields: [agentNodeSinkId], references: [id]) sinkName String + + // Default: the data coming from the source can only be consumed by the sink once, Static: input data will be reused. + isStatic Boolean @default(false) } // This model describes a component that will be executed by the AgentNode. @@ -108,6 +111,8 @@ model AgentNodeExecution { // sqlite does not support enum // enum Status { INCOMPLETE, QUEUED, RUNNING, SUCCESS, FAILED } executionStatus String + // Final JSON serialized input data for the node execution. + executionData String? addedTime DateTime @default(now()) queuedTime DateTime? startedTime DateTime? @@ -127,6 +132,9 @@ model AgentNodeExecutionInputOutput { ReferencedByInputExec AgentNodeExecution? @relation("AgentNodeExecutionInput", fields: [referencedByInputExecId], references: [id]) referencedByOutputExecId String? ReferencedByOutputExec AgentNodeExecution? @relation("AgentNodeExecutionOutput", fields: [referencedByOutputExecId], references: [id]) + + // Input and Output pin names are unique for each AgentNodeExecution. + @@unique([referencedByInputExecId, referencedByOutputExecId, name]) } // This model describes the recurring execution schedule of an Agent. diff --git a/rnd/autogpt_server/test/executor/test_manager.py b/rnd/autogpt_server/test/executor/test_manager.py index 42be39bae8b2..bd0ec70466fe 100644 --- a/rnd/autogpt_server/test/executor/test_manager.py +++ b/rnd/autogpt_server/test/executor/test_manager.py @@ -1,6 +1,8 @@ import pytest from autogpt_server.blocks.basic import ObjectLookupBlock, ValueBlock +from autogpt_server.blocks.if_block import ComparisonOperator, ConditionBlock +from autogpt_server.blocks.maths import MathsBlock, Operation from autogpt_server.data import execution, graph from autogpt_server.executor import ExecutionManager from autogpt_server.server import AgentServer @@ -9,13 +11,13 @@ async def execute_graph( + agent_server: AgentServer, test_manager: ExecutionManager, test_graph: graph.Graph, - input_data: dict[str, str], + input_data: dict, num_execs: int = 4, ) -> str: # --- Test adding new executions --- # - agent_server = AgentServer() response = await agent_server.execute_graph(test_graph.id, input_data) graph_exec_id = response["id"] @@ -24,9 +26,10 @@ async def execute_graph( return graph_exec_id -async def assert_sample_graph_executions(test_graph: graph.Graph, graph_exec_id: str): +async def assert_sample_graph_executions( + agent_server: AgentServer, test_graph: graph.Graph, graph_exec_id: str +): text = "Hello, World!" - agent_server = AgentServer() executions = await agent_server.get_run_execution_results( test_graph.id, graph_exec_id ) @@ -37,7 +40,7 @@ async def assert_sample_graph_executions(test_graph: graph.Graph, graph_exec_id: assert exec.graph_exec_id == graph_exec_id assert exec.output_data == {"output": ["Hello, World!"]} assert exec.input_data == {"input": text} - assert exec.node_id == test_graph.nodes[0].id + assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id] # Executing ConstantBlock2 exec = executions[1] @@ -45,7 +48,7 @@ async def assert_sample_graph_executions(test_graph: graph.Graph, graph_exec_id: assert exec.graph_exec_id == graph_exec_id assert exec.output_data == {"output": ["Hello, World!"]} assert exec.input_data == {"input": text} - assert exec.node_id == test_graph.nodes[1].id + assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id] # Executing TextFormatterBlock exec = executions[2] @@ -53,8 +56,11 @@ async def assert_sample_graph_executions(test_graph: graph.Graph, graph_exec_id: assert exec.graph_exec_id == graph_exec_id assert exec.output_data == {"output": ["Hello, World!,Hello, World!,!!!"]} assert exec.input_data == { + "format": "{texts[0]},{texts[1]},{texts[2]}", + "texts": ["Hello, World!", "Hello, World!", "!!!"], "texts_$_1": "Hello, World!", "texts_$_2": "Hello, World!", + "texts_$_3": "!!!", } assert exec.node_id == test_graph.nodes[2].id @@ -72,8 +78,10 @@ async def test_agent_execution(server): test_graph = create_test_graph() await graph.create_graph(test_graph) data = {"input": "Hello, World!"} - graph_exec_id = await execute_graph(server.exec_manager, test_graph, data, 4) - await assert_sample_graph_executions(test_graph, graph_exec_id) + graph_exec_id = await execute_graph( + server.agent_server, server.exec_manager, test_graph, data, 4 + ) + await assert_sample_graph_executions(server.agent_server, test_graph, graph_exec_id) @pytest.mark.asyncio(scope="session") @@ -125,10 +133,11 @@ async def test_input_pin_always_waited(server): ) test_graph = await graph.create_graph(test_graph) - graph_exec_id = await execute_graph(server.exec_manager, test_graph, {}, 3) + graph_exec_id = await execute_graph( + server.agent_server, server.exec_manager, test_graph, {}, 3 + ) - agent_server = AgentServer() - executions = await agent_server.get_run_execution_results( + executions = await server.agent_server.get_run_execution_results( test_graph.id, graph_exec_id ) assert len(executions) == 3 @@ -136,3 +145,83 @@ async def test_input_pin_always_waited(server): # Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"} assert executions[2].status == execution.ExecutionStatus.COMPLETED assert executions[2].output_data == {"output": ["value2"]} + + +@pytest.mark.asyncio(scope="session") +async def test_static_input_link_on_graph(server): + """ + This test is asserting the behaviour of static input link, e.g: reusable input link. + + Test scenario: + *ValueBlock1*===a=========\\ + *ValueBlock2*===a=====\\ || + *ValueBlock3*===a===*MathBlock*====b / static====*ValueBlock5* + *ValueBlock4*=========================================// + + In this test, there will be three input waiting in the MathBlock input pin `a`. + And later, another output is produced on input pin `b`, which is a static link, + this input will complete the input of those three incomplete executions. + """ + nodes = [ + graph.Node(block_id=ValueBlock().id, input_default={"input": 4}), # a + graph.Node(block_id=ValueBlock().id, input_default={"input": 4}), # a + graph.Node(block_id=ValueBlock().id, input_default={"input": 4}), # a + graph.Node(block_id=ValueBlock().id, input_default={"input": 5}), # b + graph.Node(block_id=ValueBlock().id), + graph.Node( + block_id=MathsBlock().id, + input_default={"operation": Operation.ADD.value}, + ), + ] + links = [ + graph.Link( + source_id=nodes[0].id, + sink_id=nodes[5].id, + source_name="output", + sink_name="a", + ), + graph.Link( + source_id=nodes[1].id, + sink_id=nodes[5].id, + source_name="output", + sink_name="a", + ), + graph.Link( + source_id=nodes[2].id, + sink_id=nodes[5].id, + source_name="output", + sink_name="a", + ), + graph.Link( + source_id=nodes[3].id, + sink_id=nodes[4].id, + source_name="output", + sink_name="input", + ), + graph.Link( + source_id=nodes[4].id, + sink_id=nodes[5].id, + source_name="output", + sink_name="b", + is_static=True, # This is the static link to test. + ), + ] + test_graph = graph.Graph( + name="TestGraph", + description="Test graph", + nodes=nodes, + links=links, + ) + + test_graph = await graph.create_graph(test_graph) + graph_exec_id = await execute_graph( + server.agent_server, server.exec_manager, test_graph, {}, 8 + ) + executions = await server.agent_server.get_run_execution_results( + test_graph.id, graph_exec_id + ) + assert len(executions) == 8 + # The last 3 executions will be a+b=4+5=9 + for exec_data in executions[-3:]: + assert exec_data.status == execution.ExecutionStatus.COMPLETED + assert exec_data.output_data == {"result": [9]} diff --git a/rnd/infra/terraform/.gitignore b/rnd/infra/terraform/.gitignore new file mode 100644 index 000000000000..ad9439c82b2a --- /dev/null +++ b/rnd/infra/terraform/.gitignore @@ -0,0 +1,4 @@ +*.tfstate +*.tfstate.backup +tfplan +.terraform/ \ No newline at end of file diff --git a/rnd/infra/terraform/.terraform.lock.hcl b/rnd/infra/terraform/.terraform.lock.hcl new file mode 100644 index 000000000000..5e14d96b4bee --- /dev/null +++ b/rnd/infra/terraform/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "4.85.0" + constraints = "~> 4.0" + hashes = [ + "h1:ZVDZuhYSIWhCkSuDkwFeSIJjn0/DcCxak2W/cHW4OQQ=", + "zh:17d60a6a6c1741cf1e09ac6731433a30950285eac88236e623ab4cbf23832ca3", + "zh:1c70254c016439dbb75cab646b4beace6ceeff117c75d81f2cc27d41c312f752", + "zh:35e2aa2cc7ac84ce55e05bb4de7b461b169d3582e56d3262e249ff09d64fe008", + "zh:417afb08d7b2744429f6b76806f4134d62b0354acf98e8a6c00de3c24f2bb6ad", + "zh:622165d09d21d9a922c86f1fc7177a400507f2a8c4a4513114407ae04da2dd29", + "zh:7cdb8e39a8ea0939558d87d2cb6caceded9e21f21003d9e9f9ce648d5db0bc3a", + "zh:851e737dc551d6004a860a8907fda65118fc2c7ede9fa828f7be704a2a39e68f", + "zh:a331ad289a02a2c4473572a573dc389be0a604cdd9e03dd8dbc10297fb14f14d", + "zh:b67fd531251380decd8dd1f849460d60f329f89df3d15f5815849a1dd001f430", + "zh:be8785957acca4f97aa3e800b313b57d1fca07788761c8867c9bc701fbe0bdb5", + "zh:cb6579a259fe020e1f88217d8f6937b2d5ace15b6406370977a1966eb31b1ca5", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/rnd/infra/terraform/environments/dev.tfvars b/rnd/infra/terraform/environments/dev.tfvars new file mode 100644 index 000000000000..cbcc69ccd8c4 --- /dev/null +++ b/rnd/infra/terraform/environments/dev.tfvars @@ -0,0 +1,12 @@ +project_id = "agpt-dev" +region = "us-central1" +zone = "us-central1-a" +network_name = "dev-gke-network" +subnet_name = "dev-gke-subnet" +subnet_cidr = "10.0.0.0/24" +cluster_name = "dev-gke-cluster" +node_count = 2 +node_pool_name = "dev-main-pool" +machine_type = "e2-medium" +disk_size_gb = 100 +static_ip_names = ["agpt-server-ip", "agpt-builder-ip", "auth-ip"] diff --git a/rnd/infra/terraform/main.tf b/rnd/infra/terraform/main.tf new file mode 100644 index 000000000000..856372e10c9f --- /dev/null +++ b/rnd/infra/terraform/main.tf @@ -0,0 +1,51 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + } + backend "gcs" { + bucket = "agpt-dev-terraform" + prefix = "terraform/state" + } +} + +provider "google" { + project = var.project_id + zone = var.zone +} + +module "static_ips" { + source = "./modules/static_ip" + + project_id = var.project_id + ip_names = var.static_ip_names + region = var.region +} + +module "networking" { + source = "./modules/networking" + + project_id = var.project_id + region = var.region + network_name = var.network_name + subnet_name = var.subnet_name + subnet_cidr = var.subnet_cidr +} + +module "gke_cluster" { + source = "./modules/gke_cluster" + + project_id = var.project_id + zone = var.zone + cluster_name = var.cluster_name + node_pool_name = var.node_pool_name + node_count = var.node_count + machine_type = var.machine_type + disk_size_gb = var.disk_size_gb + network = module.networking.network_self_link + subnetwork = module.networking.subnet_self_link + enable_autopilot = var.enable_autopilot +} diff --git a/rnd/infra/terraform/modules/gke_cluster/main.tf b/rnd/infra/terraform/modules/gke_cluster/main.tf new file mode 100644 index 000000000000..c646a14030ee --- /dev/null +++ b/rnd/infra/terraform/modules/gke_cluster/main.tf @@ -0,0 +1,21 @@ +resource "google_container_cluster" "primary" { + name = var.cluster_name + location = var.zone + + dynamic "node_pool" { + for_each = var.enable_autopilot ? [] : [1] + content { + name = var.node_pool_name + node_count = var.node_count + + node_config { + machine_type = var.machine_type + disk_size_gb = var.disk_size_gb + } + } + } + + network = var.network + subnetwork = var.subnetwork +} + diff --git a/rnd/infra/terraform/modules/gke_cluster/outputs.tf b/rnd/infra/terraform/modules/gke_cluster/outputs.tf new file mode 100644 index 000000000000..6a2c822f782d --- /dev/null +++ b/rnd/infra/terraform/modules/gke_cluster/outputs.tf @@ -0,0 +1,14 @@ +output "cluster_name" { + description = "The name of the cluster" + value = google_container_cluster.primary.name +} + +output "cluster_endpoint" { + description = "The endpoint for the cluster" + value = google_container_cluster.primary.endpoint +} + +output "node_pool_name" { + description = "The name of the node pool" + value = var.enable_autopilot ? null : google_container_cluster.primary.node_pool[0].name +} diff --git a/rnd/infra/terraform/modules/gke_cluster/variables.tf b/rnd/infra/terraform/modules/gke_cluster/variables.tf new file mode 100644 index 000000000000..80184e7ded32 --- /dev/null +++ b/rnd/infra/terraform/modules/gke_cluster/variables.tf @@ -0,0 +1,41 @@ +variable "project_id" { + description = "The project ID to host the cluster in" +} + +variable "zone" { + description = "The zone to host the cluster in" +} + +variable "cluster_name" { + description = "The name for the GKE cluster" +} + +variable "node_count" { + description = "Number of nodes in the cluster" +} + +variable "node_pool_name" { + description = "Name of the node pool in the cluster" +} + +variable "machine_type" { + description = "Type of machine to use for nodes" +} + +variable "disk_size_gb" { + description = "Size of the disk attached to each node, specified in GB" + default = 100 +} + +variable "network" { + description = "The VPC network to host the cluster in" +} + +variable "subnetwork" { + description = "The subnetwork to host the cluster in" +} + +variable "enable_autopilot" { + description = "Enable Autopilot for this cluster" + type = bool +} \ No newline at end of file diff --git a/rnd/infra/terraform/modules/networking/main.tf b/rnd/infra/terraform/modules/networking/main.tf new file mode 100644 index 000000000000..64aac3a77d8c --- /dev/null +++ b/rnd/infra/terraform/modules/networking/main.tf @@ -0,0 +1,12 @@ +resource "google_compute_network" "vpc_network" { + name = var.network_name + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnet" { + name = var.subnet_name + ip_cidr_range = var.subnet_cidr + region = var.region + network = google_compute_network.vpc_network.self_link +} + diff --git a/rnd/infra/terraform/modules/networking/ouputs.tf b/rnd/infra/terraform/modules/networking/ouputs.tf new file mode 100644 index 000000000000..24db7330e949 --- /dev/null +++ b/rnd/infra/terraform/modules/networking/ouputs.tf @@ -0,0 +1,19 @@ +output "network_name" { + description = "The name of the VPC network" + value = google_compute_network.vpc_network.name +} + +output "network_self_link" { + description = "The self-link of the VPC network" + value = google_compute_network.vpc_network.self_link +} + +output "subnet_name" { + description = "The name of the subnet" + value = google_compute_subnetwork.subnet.name +} + +output "subnet_self_link" { + description = "The self-link of the subnet" + value = google_compute_subnetwork.subnet.self_link +} \ No newline at end of file diff --git a/rnd/infra/terraform/modules/networking/variables.tf b/rnd/infra/terraform/modules/networking/variables.tf new file mode 100644 index 000000000000..3989bddff0de --- /dev/null +++ b/rnd/infra/terraform/modules/networking/variables.tf @@ -0,0 +1,21 @@ +variable "project_id" { + description = "The project ID to host the network in" +} + +variable "region" { + description = "The region to host the network in" +} + +variable "network_name" { + description = "The name of the VPC network" +} + +variable "subnet_name" { + description = "The name of the subnet" +} + +variable "subnet_cidr" { + description = "The CIDR range for the subnet" +} + + diff --git a/rnd/infra/terraform/modules/static_ip/main.tf b/rnd/infra/terraform/modules/static_ip/main.tf new file mode 100644 index 000000000000..4b6d0735419a --- /dev/null +++ b/rnd/infra/terraform/modules/static_ip/main.tf @@ -0,0 +1,6 @@ +resource "google_compute_address" "static_ip" { + count = length(var.ip_names) + name = "${var.project_id}-${var.ip_names[count.index]}" + region = var.region + address_type = "EXTERNAL" +} \ No newline at end of file diff --git a/rnd/infra/terraform/modules/static_ip/outputs.tf b/rnd/infra/terraform/modules/static_ip/outputs.tf new file mode 100644 index 000000000000..2b03fe3623a4 --- /dev/null +++ b/rnd/infra/terraform/modules/static_ip/outputs.tf @@ -0,0 +1,9 @@ +output "ip_addresses" { + description = "Map of created static IP addresses" + value = { for i, ip in google_compute_address.static_ip : var.ip_names[i] => ip.address } +} + +output "ip_names" { + description = "List of full names of the created static IP addresses" + value = google_compute_address.static_ip[*].name +} \ No newline at end of file diff --git a/rnd/infra/terraform/modules/static_ip/variables.tf b/rnd/infra/terraform/modules/static_ip/variables.tf new file mode 100644 index 000000000000..4cc6607a4dd1 --- /dev/null +++ b/rnd/infra/terraform/modules/static_ip/variables.tf @@ -0,0 +1,14 @@ +variable "project_id" { + description = "The project ID to prepend to IP names" + type = string +} + +variable "ip_names" { + description = "List of custom names for static IPs" + type = list(string) +} + +variable "region" { + description = "Region to create the static IPs in" + type = string +} diff --git a/rnd/infra/terraform/variables.tf b/rnd/infra/terraform/variables.tf new file mode 100644 index 000000000000..66c35e7ad62f --- /dev/null +++ b/rnd/infra/terraform/variables.tf @@ -0,0 +1,76 @@ +variable "project_id" { + description = "The project ID to host the cluster in" + type = string +} + +variable "region" { + description = "Project region" + type = string + default = "us-central1" +} + +variable "zone" { + description = "The zone to host the cluster in" + type = string + default = "us-central1-a" +} + +variable "network_name" { + description = "The name of the VPC network" + type = string + default = "gke-network" +} + +variable "subnet_name" { + description = "The name of the subnet" + type = string + default = "gke-subnet" +} + +variable "subnet_cidr" { + description = "The CIDR range for the subnet" + type = string + default = "10.0.0.0/24" +} + +variable "cluster_name" { + description = "The name for the GKE cluster" + type = string + default = "gke-cluster" +} + +variable "node_count" { + description = "Number of nodes in the cluster" + type = number + default = 3 +} + +variable "node_pool_name" { + description = "The name for the node pool" + type = string + default = "default-pool" +} + +variable "machine_type" { + description = "Type of machine to use for nodes" + type = string + default = "e2-medium" +} + +variable "disk_size_gb" { + description = "Size of the disk attached to each node, specified in GB" + type = number + default = 100 +} + +variable "enable_autopilot" { + description = "Enable Autopilot for this cluster" + type = bool + default = false +} + +variable "static_ip_names" { + description = "List of custom names for static IPs" + type = list(string) + default = ["ip-1", "ip-2", "ip-3"] +} diff --git a/rnd/market/.env.example b/rnd/market/.env.example new file mode 100644 index 000000000000..3d982c3e5ce5 --- /dev/null +++ b/rnd/market/.env.example @@ -0,0 +1,7 @@ +RUN_ENV=local +DB_USER=marketplace +DB_PASS=pass123 +DB_NAME=marketplace +DB_PORT=5432 +DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${DB_NAME} +SENTRY_DSN=https://sentry.io \ No newline at end of file diff --git a/rnd/market/.gitignore b/rnd/market/.gitignore new file mode 100644 index 000000000000..7fd0341ba08e --- /dev/null +++ b/rnd/market/.gitignore @@ -0,0 +1,6 @@ +database.db +database.db-journal +build/ +config.json +secrets/* +!secrets/.gitkeep \ No newline at end of file diff --git a/rnd/market/README.md b/rnd/market/README.md new file mode 100644 index 000000000000..8b9bff449d82 --- /dev/null +++ b/rnd/market/README.md @@ -0,0 +1,14 @@ +# AutoGPT Agent Marketplace + +## Overview +AutoGPT Agent Marketplace is an open-source platform for autonomous AI agents. This project aims to create a user-friendly, accessible marketplace where users can discover, utilize, and contribute to a diverse ecosystem of AI solutions. + +## Vision +Our vision is to empower users with customizable and free AI agents, fostering an open-source community that drives innovation in AI automation across various industries. + +# Key Features + +- Agent Discovery and Search +- Agent Listings with Detailed Information +- User Profiles +- Data Protection and Compliance \ No newline at end of file diff --git a/rnd/market/docker-compose.yml b/rnd/market/docker-compose.yml new file mode 100644 index 000000000000..3eaa4089277d --- /dev/null +++ b/rnd/market/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3" +services: + postgres: + image: ankane/pgvector:latest + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASS} + POSTGRES_DB: ${DB_NAME} + PGUSER: ${DB_USER} + healthcheck: + test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${DB_PORT}:5432" diff --git a/rnd/market/linter.py b/rnd/market/linter.py new file mode 100644 index 000000000000..83c574b03494 --- /dev/null +++ b/rnd/market/linter.py @@ -0,0 +1,27 @@ +import os +import subprocess + +directory = os.path.dirname(os.path.realpath(__file__)) + + +def run(*command: str) -> None: + print(f">>>>> Running poetry run {' '.join(command)}") + subprocess.run(["poetry", "run"] + list(command), cwd=directory, check=True) + + +def lint(): + try: + run("ruff", "check", ".", "--exit-zero") + run("isort", "--diff", "--check", "--profile", "black", ".") + run("black", "--diff", "--check", ".") + run("pyright") + except subprocess.CalledProcessError as e: + print("Lint failed, try running `poetry run format` to fix the issues: ", e) + raise e + + +def format(): + run("ruff", "check", "--fix", ".") + run("isort", "--profile", "black", ".") + run("black", ".") + run("pyright", ".") diff --git a/rnd/market/market/__init__.py b/rnd/market/market/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rnd/market/market/app.py b/rnd/market/market/app.py new file mode 100644 index 000000000000..48209f505fb0 --- /dev/null +++ b/rnd/market/market/app.py @@ -0,0 +1,60 @@ + +from contextlib import asynccontextmanager +import os +from dotenv import load_dotenv +from fastapi import FastAPI +from fastapi.middleware.gzip import GZipMiddleware +from prisma import Prisma +import sentry_sdk +from sentry_sdk.integrations.asyncio import AsyncioIntegration +from sentry_sdk.integrations.fastapi import FastApiIntegration +from sentry_sdk.integrations.starlette import StarletteIntegration + +from market.routes import agents + +load_dotenv() + +if os.environ.get("SENTRY_DSN"): + sentry_sdk.init( + dsn=os.environ.get("SENTRY_DSN"), + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for performance monitoring. + traces_sample_rate=1.0, + # Set profiles_sample_rate to 1.0 to profile 100% + # of sampled transactions. + # We recommend adjusting this value in production. + profiles_sample_rate=1.0, + enable_tracing=True, + environment=os.environ.get("RUN_ENV", default="CLOUD").lower(), + integrations=[ + StarletteIntegration(transaction_style="url"), + FastApiIntegration(transaction_style="url"), + AsyncioIntegration(), + ], + ) + +db_client = Prisma(auto_register=True) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + await db_client.connect() + yield + await db_client.disconnect() + + +app = FastAPI( + title="Marketplace API", + description=( + "AutoGPT Marketplace API is a service that allows users to share AI agents." + ), + summary="Maketplace API", + version="0.1", + lifespan=lifespan, +) + +# Add gzip middleware to compress responses +app.add_middleware(GZipMiddleware, minimum_size=1000) + + +app.include_router(agents.router, prefix="/market/agents", tags=["agents"]) diff --git a/rnd/market/market/routes/agents.py b/rnd/market/market/routes/agents.py new file mode 100644 index 000000000000..af9233c561e3 --- /dev/null +++ b/rnd/market/market/routes/agents.py @@ -0,0 +1,3 @@ +from fastapi import APIRouter + +router = APIRouter() diff --git a/rnd/market/poetry.lock b/rnd/market/poetry.lock new file mode 100644 index 000000000000..20089dfe0e48 --- /dev/null +++ b/rnd/market/poetry.lock @@ -0,0 +1,1491 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.111.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"}, + {file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"}, +] + +[package.dependencies] +email_validator = ">=2.0.0" +fastapi-cli = ">=0.0.2" +httpx = ">=0.23.0" +jinja2 = ">=2.11.2" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +python-multipart = ">=0.0.7" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" +uvicorn = {version = ">=0.12.0", extras = ["standard"]} + +[package.extras] +all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastapi-cli" +version = "0.0.4" +description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"}, + {file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"}, +] + +[package.dependencies] +typer = ">=0.12.3" + +[package.extras] +standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httptools" +version = "0.6.1" +description = "A collection of framework independent HTTP protocol utils." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, + {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, + {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, + {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, + {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, + {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, + {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, + {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, + {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, + {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, + {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, + {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, + {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, + {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, + {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, + {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, + {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, + {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, + {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, + {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, + {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, + {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, +] + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prisma" +version = "0.12.0" +description = "Prisma Client Python is an auto-generated and fully type-safe database client" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prisma-0.12.0-py3-none-any.whl", hash = "sha256:4b90438bb8030f71a33bfb66fdf2ce1e342a28ee524e4c2702d9f73643571d6f"}, + {file = "prisma-0.12.0.tar.gz", hash = "sha256:ec22804caae37555bd9ffa5efdcc630b2bed77187d13da5269715b1ad51a35f9"}, +] + +[package.dependencies] +click = ">=7.1.2" +httpx = ">=0.19.0" +jinja2 = ">=2.11.2" +nodeenv = "*" +pydantic = ">=1.8.0,<3" +python-dotenv = ">=0.12.0" +StrEnum = {version = "*", markers = "python_version < \"3.11\""} +tomlkit = "*" +typing-extensions = ">=4.0.1" + +[package.extras] +all = ["nodejs-bin"] +node = ["nodejs-bin"] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyright" +version = "1.1.373" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.373-py3-none-any.whl", hash = "sha256:b805413227f2c209f27b14b55da27fe5e9fb84129c9f1eb27708a5d12f6f000e"}, + {file = "pyright-1.1.373.tar.gz", hash = "sha256:f41bcfc8b9d1802b09921a394d6ae1ce19694957b628bc657629688daf8a83ff"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-watcher" +version = "0.4.2" +description = "Automatically rerun your tests on file modifications" +optional = false +python-versions = "<4.0.0,>=3.7.0" +files = [ + {file = "pytest_watcher-0.4.2-py3-none-any.whl", hash = "sha256:a43949ba67dd8d7e1fd0de5eea44a999081f0aec9f93b4e744264b4c6a3d9bbe"}, + {file = "pytest_watcher-0.4.2.tar.gz", hash = "sha256:7b292f025ca19617cd7567c228c6187b5087f2da9e4d2cf6e144e5764a0471b0"}, +] + +[package.dependencies] +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +watchdog = ">=2.0.0" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-multipart" +version = "0.0.9" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, + {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, +] + +[package.extras] +dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.5.5" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"}, + {file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"}, + {file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"}, + {file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"}, + {file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"}, + {file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"}, + {file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"}, +] + +[[package]] +name = "sentry-sdk" +version = "2.11.0" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sentry_sdk-2.11.0-py2.py3-none-any.whl", hash = "sha256:d964710e2dbe015d9dc4ff0ad16225d68c3b36936b742a6fe0504565b760a3b7"}, + {file = "sentry_sdk-2.11.0.tar.gz", hash = "sha256:4ca16e9f5c7c6bc2fb2d5c956219f4926b148e511fffdbbde711dc94f1e0468f"}, +] + +[package.dependencies] +certifi = "*" +fastapi = {version = ">=0.79.0", optional = true, markers = "extra == \"fastapi\""} +urllib3 = ">=1.26.11" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=6)"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "strenum" +version = "0.4.15" +description = "An Enum that inherits from str." +optional = false +python-versions = "*" +files = [ + {file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"}, + {file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"}, +] + +[package.extras] +docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] +release = ["twine"] +test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.0" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, + {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, +] + +[[package]] +name = "typer" +version = "0.12.3" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, + {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.30.3" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.30.3-py3-none-any.whl", hash = "sha256:94a3608da0e530cea8f69683aa4126364ac18e3826b6630d1a65f4638aade503"}, + {file = "uvicorn-0.30.3.tar.gz", hash = "sha256:0d114d0831ff1adbf231d358cbf42f17333413042552a624ea6a9b4c33dcfd81"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} +h11 = ">=0.8" +httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} +uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} +websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "uvloop" +version = "0.19.0" +description = "Fast implementation of asyncio event loop on top of libuv" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, + {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, + {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, + {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, + {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, + {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, + {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, + {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, + {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, + {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, + {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, + {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, + {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, + {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, + {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, + {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, + {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] + +[[package]] +name = "watchdog" +version = "4.0.1" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "watchfiles" +version = "0.22.0" +description = "Simple, modern and high performance file watching and code reload in python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"}, + {file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"}, + {file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"}, + {file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"}, + {file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"}, + {file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"}, + {file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"}, + {file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"}, + {file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"}, + {file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"}, + {file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"}, + {file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"}, + {file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"}, + {file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"}, + {file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"}, + {file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"}, + {file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"}, + {file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d47e9ef1a94cc7a536039e46738e17cce058ac1593b2eccdede8bf72e45f372a"}, + {file = "watchfiles-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28f393c1194b6eaadcdd8f941307fc9bbd7eb567995232c830f6aef38e8a6e88"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd64f3a4db121bc161644c9e10a9acdb836853155a108c2446db2f5ae1778c3d"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2abeb79209630da981f8ebca30a2c84b4c3516a214451bfc5f106723c5f45843"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cc382083afba7918e32d5ef12321421ef43d685b9a67cc452a6e6e18920890e"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d048ad5d25b363ba1d19f92dcf29023988524bee6f9d952130b316c5802069cb"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:103622865599f8082f03af4214eaff90e2426edff5e8522c8f9e93dc17caee13"}, + {file = "watchfiles-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e1f3cf81f1f823e7874ae563457828e940d75573c8fbf0ee66818c8b6a9099"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8597b6f9dc410bdafc8bb362dac1cbc9b4684a8310e16b1ff5eee8725d13dcd6"}, + {file = "watchfiles-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b04a2cbc30e110303baa6d3ddce8ca3664bc3403be0f0ad513d1843a41c97d1"}, + {file = "watchfiles-0.22.0-cp38-none-win32.whl", hash = "sha256:b610fb5e27825b570554d01cec427b6620ce9bd21ff8ab775fc3a32f28bba63e"}, + {file = "watchfiles-0.22.0-cp38-none-win_amd64.whl", hash = "sha256:fe82d13461418ca5e5a808a9e40f79c1879351fcaeddbede094028e74d836e86"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3973145235a38f73c61474d56ad6199124e7488822f3a4fc97c72009751ae3b0"}, + {file = "watchfiles-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:280a4afbc607cdfc9571b9904b03a478fc9f08bbeec382d648181c695648202f"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a0d883351a34c01bd53cfa75cd0292e3f7e268bacf2f9e33af4ecede7e21d1d"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9165bcab15f2b6d90eedc5c20a7f8a03156b3773e5fb06a790b54ccecdb73385"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc1b9b56f051209be458b87edb6856a449ad3f803315d87b2da4c93b43a6fe72"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dc1fc25a1dedf2dd952909c8e5cb210791e5f2d9bc5e0e8ebc28dd42fed7562"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc92d2d2706d2b862ce0568b24987eba51e17e14b79a1abcd2edc39e48e743c8"}, + {file = "watchfiles-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b94e14b88409c58cdf4a8eaf0e67dfd3ece7e9ce7140ea6ff48b0407a593ec"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96eec15e5ea7c0b6eb5bfffe990fc7c6bd833acf7e26704eb18387fb2f5fd087"}, + {file = "watchfiles-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:28324d6b28bcb8d7c1041648d7b63be07a16db5510bea923fc80b91a2a6cbed6"}, + {file = "watchfiles-0.22.0-cp39-none-win32.whl", hash = "sha256:8c3e3675e6e39dc59b8fe5c914a19d30029e36e9f99468dddffd432d8a7b1c93"}, + {file = "watchfiles-0.22.0-cp39-none-win_amd64.whl", hash = "sha256:25c817ff2a86bc3de3ed2df1703e3d24ce03479b27bb4527c57e722f8554d971"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"}, + {file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"}, + {file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"}, + {file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"}, + {file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"}, +] + +[package.dependencies] +anyio = ">=3.0.0" + +[[package]] +name = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "8f44ca82bd8d4a16c3f644cff2421d14f5aa6a36da0ba683b7ac9e883b4212b5" diff --git a/rnd/market/pyproject.toml b/rnd/market/pyproject.toml new file mode 100644 index 000000000000..ad00cfeceabf --- /dev/null +++ b/rnd/market/pyproject.toml @@ -0,0 +1,45 @@ +[tool.poetry] +name = "market" +version = "0.1.0" +description = "" +authors = ["SwiftyOS "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +prisma = "^0.12.0" +python-dotenv = "^1.0.1" +uvicorn = "^0.30.3" +fastapi = "^0.111.1" +sentry-sdk = {extras = ["fastapi"], version = "^2.11.0"} + +[tool.poetry.group.dev.dependencies] +pytest = "^8.2.1" +pytest-asyncio = "^0.23.7" + +pytest-watcher = "^0.4.2" +requests = "^2.32.3" +ruff = "^0.5.2" +pyright = "^1.1.371" +isort = "^5.13.2" +black = "^24.4.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +format = "linter:format" +lint = "linter:lint" + +[tool.pytest-watcher] +now = false +clear = true +delay = 0.2 +runner = "pytest" +runner_args = [] +patterns = ["*.py"] +ignore_patterns = [] + +[tool.pytest.ini_options] +asyncio_mode = "auto" diff --git a/rnd/market/schema.prisma b/rnd/market/schema.prisma new file mode 100644 index 000000000000..e1c71cc4b2b0 --- /dev/null +++ b/rnd/market/schema.prisma @@ -0,0 +1,78 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-py" + recursive_type_depth = 5 + interface = "asyncio" +} + +// This model describes the Agent Graph/Flow (Multi Agent System). +model AgentGraph { + id String @default(uuid()) + version Int @default(1) + + name String? + description String? + isActive Boolean @default(true) + isTemplate Boolean @default(false) + + AgentNodes AgentNode[] + + @@id(name: "graphVersionId", [id, version]) +} + +// This model describes a single node in the Agent Graph/Flow (Multi Agent System). +model AgentNode { + id String @id @default(uuid()) + + agentBlockId String + AgentBlock AgentBlock @relation(fields: [agentBlockId], references: [id]) + + agentGraphId String + agentGraphVersion Int @default(1) + AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version]) + + // List of consumed input, that the parent node should provide. + Input AgentNodeLink[] @relation("AgentNodeSink") + + // List of produced output, that the child node should be executed. + Output AgentNodeLink[] @relation("AgentNodeSource") + + // JSON serialized dict[str, str] containing predefined input values. + constantInput String @default("{}") + + // JSON serialized dict[str, str] containing the node metadata. + metadata String @default("{}") +} + +// This model describes the link between two AgentNodes. +model AgentNodeLink { + id String @id @default(uuid()) + + // Output of a node is connected to the source of the link. + agentNodeSourceId String + AgentNodeSource AgentNode @relation("AgentNodeSource", fields: [agentNodeSourceId], references: [id]) + sourceName String + + // Input of a node is connected to the sink of the link. + agentNodeSinkId String + AgentNodeSink AgentNode @relation("AgentNodeSink", fields: [agentNodeSinkId], references: [id]) + sinkName String +} + +// This model describes a component that will be executed by the AgentNode. +model AgentBlock { + id String @id @default(uuid()) + name String @unique + + // We allow a block to have multiple types of input & output. + // Serialized object-typed `jsonschema` with top-level properties as input/output name. + inputSchema String + outputSchema String + + // Prisma requires explicit back-references. + ReferencedByAgentNode AgentNode[] +}