diff --git a/.github/workflows/platform-frontend-ci.yml b/.github/workflows/platform-frontend-ci.yml index 97b4d38e06..486a7d07f5 100644 --- a/.github/workflows/platform-frontend-ci.yml +++ b/.github/workflows/platform-frontend-ci.yml @@ -55,6 +55,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Generate API client + run: pnpm generate:api-client + - name: Run tsc check run: pnpm type-check diff --git a/.gitignore b/.gitignore index d00ab276ce..b6c2fdc035 100644 --- a/.gitignore +++ b/.gitignore @@ -165,7 +165,7 @@ package-lock.json # Allow for locally private items # private -pri* +pri* # ignore ig* .github_access_token @@ -176,3 +176,7 @@ autogpt_platform/backend/settings.py *.ign.* .test-contents +.claude/settings.local.json + +# Auto generated client +autogpt_platform/frontend/src/api/__generated__ diff --git a/autogpt_platform/CLAUDE.md b/autogpt_platform/CLAUDE.md index 30bb2496bd..93f64e1eb3 100644 --- a/autogpt_platform/CLAUDE.md +++ b/autogpt_platform/CLAUDE.md @@ -32,6 +32,7 @@ poetry run test poetry run pytest path/to/test_file.py::test_function_name # Lint and format +# prefer format if you want to just "fix" it and only get the errors that can't be autofixed poetry run format # Black + isort poetry run lint # ruff ``` @@ -77,6 +78,7 @@ npm run type-check - **Queue System**: RabbitMQ for async task processing - **Execution Engine**: Separate executor service processes agent workflows - **Authentication**: JWT-based with Supabase integration +- **Security**: Cache protection middleware prevents sensitive data caching in browsers/proxies ### Frontend Architecture - **Framework**: Next.js App Router with React Server Components @@ -129,4 +131,15 @@ Key models (defined in `/backend/schema.prisma`): 1. Components go in `/frontend/src/components/` 2. Use existing UI components from `/frontend/src/components/ui/` 3. Add Storybook stories for new components -4. Test with Playwright if user-facing \ No newline at end of file +4. Test with Playwright if user-facing + +### Security Implementation + +**Cache Protection Middleware:** +- Located in `/backend/backend/server/middleware/security.py` +- Default behavior: Disables caching for ALL endpoints with `Cache-Control: no-store, no-cache, must-revalidate, private` +- Uses an allow list approach - only explicitly permitted paths can be cached +- Cacheable paths include: static assets (`/static/*`, `/_next/static/*`), health checks, public store pages, documentation +- Prevents sensitive data (auth tokens, API keys, user data) from being cached by browsers/proxies +- To allow caching for a new endpoint, add it to `CACHEABLE_PATHS` in the middleware +- Applied to both main API server and external API applications \ No newline at end of file diff --git a/autogpt_platform/README.md b/autogpt_platform/README.md index 6d535d5543..8422a29f0e 100644 --- a/autogpt_platform/README.md +++ b/autogpt_platform/README.md @@ -62,6 +62,12 @@ To run the AutoGPT Platform, follow these steps: pnpm i ``` + Generate the API client (this step is required before running the frontend): + + ``` + pnpm generate:api-client + ``` + Then start the frontend application in development mode: ``` @@ -164,3 +170,27 @@ To persist data for PostgreSQL and Redis, you can modify the `docker-compose.yml 3. Save the file and run `docker compose up -d` to apply the changes. This configuration will create named volumes for PostgreSQL and Redis, ensuring that your data persists across container restarts. + +### API Client Generation + +The platform includes scripts for generating and managing the API client: + +- `pnpm fetch:openapi`: Fetches the OpenAPI specification from the backend service (requires backend to be running on port 8006) +- `pnpm generate:api-client`: Generates the TypeScript API client from the OpenAPI specification using Orval +- `pnpm generate:api-all`: Runs both fetch and generate commands in sequence + +#### Manual API Client Updates + +If you need to update the API client after making changes to the backend API: + +1. Ensure the backend services are running: + ``` + docker compose up -d + ``` + +2. Generate the updated API client: + ``` + pnpm generate:api-all + ``` + +This will fetch the latest OpenAPI specification and regenerate the TypeScript client code. diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py b/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py index 257250a753..0ac5f8793c 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py @@ -31,4 +31,5 @@ class APIKeyManager: """Verify if a provided API key matches the stored hash.""" if not provided_key.startswith(self.PREFIX): return False - return hashlib.sha256(provided_key.encode()).hexdigest() == stored_hash + provided_hash = hashlib.sha256(provided_key.encode()).hexdigest() + return secrets.compare_digest(provided_hash, stored_hash) diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/auth/middleware.py b/autogpt_platform/autogpt_libs/autogpt_libs/auth/middleware.py index d00fe1a05d..eb583ac1fc 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/auth/middleware.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/auth/middleware.py @@ -1,5 +1,6 @@ import inspect import logging +import secrets from typing import Any, Callable, Optional from fastapi import HTTPException, Request, Security @@ -93,7 +94,11 @@ class APIKeyValidator: self.error_message = error_message async def default_validator(self, api_key: str) -> bool: - return api_key == self.expected_token + if not self.expected_token: + raise ValueError( + "Expected Token Required to be set when uisng API Key Validator default validation" + ) + return secrets.compare_digest(api_key, self.expected_token) async def __call__( self, request: Request, api_key: str = Security(APIKeyHeader) diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/utils/synchronize.py b/autogpt_platform/autogpt_libs/autogpt_libs/utils/synchronize.py index d8221eea0f..348ae4d78d 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/utils/synchronize.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/utils/synchronize.py @@ -1,15 +1,15 @@ -from contextlib import contextmanager -from threading import Lock +import asyncio +from contextlib import asynccontextmanager from typing import TYPE_CHECKING, Any from expiringdict import ExpiringDict if TYPE_CHECKING: - from redis import Redis - from redis.lock import Lock as RedisLock + from redis.asyncio import Redis as AsyncRedis + from redis.asyncio.lock import Lock as AsyncRedisLock -class RedisKeyedMutex: +class AsyncRedisKeyedMutex: """ This class provides a mutex that can be locked and unlocked by a specific key, using Redis as a distributed locking provider. @@ -17,41 +17,45 @@ class RedisKeyedMutex: in case the key is not unlocked for a specified duration, to prevent memory leaks. """ - def __init__(self, redis: "Redis", timeout: int | None = 60): + def __init__(self, redis: "AsyncRedis", timeout: int | None = 60): self.redis = redis self.timeout = timeout - self.locks: dict[Any, "RedisLock"] = ExpiringDict( + self.locks: dict[Any, "AsyncRedisLock"] = ExpiringDict( max_len=6000, max_age_seconds=self.timeout ) - self.locks_lock = Lock() + self.locks_lock = asyncio.Lock() - @contextmanager - def locked(self, key: Any): - lock = self.acquire(key) + @asynccontextmanager + async def locked(self, key: Any): + lock = await self.acquire(key) try: yield finally: - if lock.locked() and lock.owned(): - lock.release() + if (await lock.locked()) and (await lock.owned()): + await lock.release() - def acquire(self, key: Any) -> "RedisLock": + async def acquire(self, key: Any) -> "AsyncRedisLock": """Acquires and returns a lock with the given key""" - with self.locks_lock: + async with self.locks_lock: if key not in self.locks: self.locks[key] = self.redis.lock( str(key), self.timeout, thread_local=False ) lock = self.locks[key] - lock.acquire() + await lock.acquire() return lock - def release(self, key: Any): - if (lock := self.locks.get(key)) and lock.locked() and lock.owned(): - lock.release() + async def release(self, key: Any): + if ( + (lock := self.locks.get(key)) + and (await lock.locked()) + and (await lock.owned()) + ): + await lock.release() - def release_all_locks(self): + async def release_all_locks(self): """Call this on process termination to ensure all locks are released""" - self.locks_lock.acquire(blocking=False) - for lock in self.locks.values(): - if lock.locked() and lock.owned(): - lock.release() + async with self.locks_lock: + for lock in self.locks.values(): + if (await lock.locked()) and (await lock.owned()): + await lock.release() diff --git a/autogpt_platform/autogpt_libs/poetry.lock b/autogpt_platform/autogpt_libs/poetry.lock index 12a4970d50..155098d059 100644 --- a/autogpt_platform/autogpt_libs/poetry.lock +++ b/autogpt_platform/autogpt_libs/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -177,7 +177,7 @@ files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -markers = {main = "python_version < \"3.11\"", dev = "python_full_version < \"3.11.3\""} +markers = {main = "python_version == \"3.10\"", dev = "python_full_version < \"3.11.3\""} [[package]] name = "attrs" @@ -323,6 +323,21 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -375,7 +390,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -399,6 +414,27 @@ files = [ [package.extras] tests = ["coverage", "coveralls", "dill", "mock", "nose"] +[[package]] +name = "fastapi" +version = "0.115.12" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, + {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, +] + +[package.dependencies] +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" +starlette = ">=0.40.0,<0.47.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "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)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + [[package]] name = "frozenlist" version = "1.4.1" @@ -895,6 +931,47 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "launchdarkly-eventsource" +version = "1.2.4" +description = "LaunchDarkly SSE Client" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "launchdarkly_eventsource-1.2.4-py3-none-any.whl", hash = "sha256:048ef8c4440d0d8219778661ee4d4b5e12aa6ed2c29a3004417ede44c2386e8c"}, + {file = "launchdarkly_eventsource-1.2.4.tar.gz", hash = "sha256:b8b9342681f55e1d35c56243431cbbaca4eb9812d6785f8de204af322104e066"}, +] + +[package.dependencies] +urllib3 = ">=1.26.0,<3" + +[[package]] +name = "launchdarkly-server-sdk" +version = "9.11.1" +description = "LaunchDarkly SDK for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "launchdarkly_server_sdk-9.11.1-py3-none-any.whl", hash = "sha256:128569cebf666dd115cc0ba03c48ff75f6acc9788301a7e2c3a54d06107e445a"}, + {file = "launchdarkly_server_sdk-9.11.1.tar.gz", hash = "sha256:150e29656cb8c506d1967f3c59e62b69310d345ec27217640a6146dd1db5d250"}, +] + +[package.dependencies] +certifi = ">=2018.4.16" +expiringdict = ">=1.1.4" +launchdarkly-eventsource = ">=1.2.4,<2.0.0" +pyRFC3339 = ">=1.0" +semver = ">=2.10.2" +urllib3 = ">=1.26.0,<3" + +[package.extras] +consul = ["python-consul (>=1.0.1)"] +dynamodb = ["boto3 (>=1.9.71)"] +redis = ["redis (>=2.10.5)"] +test-filesource = ["pyyaml (>=5.3.1)", "watchdog (>=3.0.0)"] + [[package]] name = "multidict" version = "6.1.0" @@ -1412,6 +1489,18 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pyrfc3339" +version = "2.0.1" +description = "Generate and parse RFC 3339 timestamps" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pyRFC3339-2.0.1-py3-none-any.whl", hash = "sha256:30b70a366acac3df7386b558c21af871522560ed7f3f73cf344b8c2cbb8b0c9d"}, + {file = "pyrfc3339-2.0.1.tar.gz", hash = "sha256:e47843379ea35c1296c3b6c67a948a1a490ae0584edfcbdea0eaffb5dd29960b"}, +] + [[package]] name = "pytest" version = "8.3.3" @@ -1604,6 +1693,18 @@ files = [ {file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"}, ] +[[package]] +name = "semver" +version = "3.0.4" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, + {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, +] + [[package]] name = "six" version = "1.16.0" @@ -1628,6 +1729,24 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "starlette" +version = "0.46.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, + {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + [[package]] name = "storage3" version = "0.11.0" @@ -1704,7 +1823,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, @@ -1755,6 +1874,26 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "uvicorn" +version = "0.34.3" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885"}, + {file = "uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] + [[package]] name = "websockets" version = "12.0" @@ -2037,4 +2176,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "78ebf65cdef769cfbe92fe204f01e32d219cca9ee5a6ca9e657aa0630be63802" +content-hash = "d92143928a88ca3a56ac200c335910eafac938940022fed8bd0d17c95040b54f" diff --git a/autogpt_platform/autogpt_libs/pyproject.toml b/autogpt_platform/autogpt_libs/pyproject.toml index 2f2d05ac7a..71d6eeb1f6 100644 --- a/autogpt_platform/autogpt_libs/pyproject.toml +++ b/autogpt_platform/autogpt_libs/pyproject.toml @@ -17,6 +17,9 @@ pyjwt = "^2.10.1" pytest-asyncio = "^0.26.0" pytest-mock = "^3.14.0" supabase = "^2.15.1" +launchdarkly-server-sdk = "^9.11.1" +fastapi = "^0.115.12" +uvicorn = "^0.34.3" [tool.poetry.group.dev.dependencies] redis = "^5.2.1" diff --git a/autogpt_platform/backend/.env.example b/autogpt_platform/backend/.env.example index 4c5830d704..18343d7725 100644 --- a/autogpt_platform/backend/.env.example +++ b/autogpt_platform/backend/.env.example @@ -13,7 +13,6 @@ PRISMA_SCHEMA="postgres/schema.prisma" # EXECUTOR NUM_GRAPH_WORKERS=10 -NUM_NODE_WORKERS=3 BACKEND_CORS_ALLOW_ORIGINS=["http://localhost:3000"] diff --git a/autogpt_platform/backend/backend/blocks/agent.py b/autogpt_platform/backend/backend/blocks/agent.py index a68cb05204..c25d99458d 100644 --- a/autogpt_platform/backend/backend/blocks/agent.py +++ b/autogpt_platform/backend/backend/blocks/agent.py @@ -1,3 +1,4 @@ +import asyncio import logging from typing import Any, Optional @@ -61,37 +62,78 @@ class AgentExecutorBlock(Block): categories={BlockCategory.AGENT}, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: - from backend.data.execution import ExecutionEventType + async def run(self, input_data: Input, **kwargs) -> BlockOutput: + from backend.executor import utils as execution_utils - event_bus = execution_utils.get_execution_event_bus() - - graph_exec = execution_utils.add_graph_execution( + graph_exec = await execution_utils.add_graph_execution( graph_id=input_data.graph_id, graph_version=input_data.graph_version, user_id=input_data.user_id, inputs=input_data.inputs, node_credentials_input_map=input_data.node_credentials_input_map, + use_db_query=False, ) - log_id = f"Graph #{input_data.graph_id}-V{input_data.graph_version}, exec-id: {graph_exec.id}" + + try: + async for name, data in self._run( + graph_id=input_data.graph_id, + graph_version=input_data.graph_version, + graph_exec_id=graph_exec.id, + user_id=input_data.user_id, + ): + yield name, data + except asyncio.CancelledError: + logger.warning( + f"Execution of graph {input_data.graph_id} version {input_data.graph_version} was cancelled." + ) + await execution_utils.stop_graph_execution( + graph_exec.id, use_db_query=False + ) + except Exception as e: + logger.error( + f"Execution of graph {input_data.graph_id} version {input_data.graph_version} failed: {e}, stopping execution." + ) + await execution_utils.stop_graph_execution( + graph_exec.id, use_db_query=False + ) + raise + + async def _run( + self, + graph_id: str, + graph_version: int, + graph_exec_id: str, + user_id: str, + ) -> BlockOutput: + + from backend.data.execution import ExecutionEventType + from backend.executor import utils as execution_utils + + event_bus = execution_utils.get_async_execution_event_bus() + + log_id = f"Graph #{graph_id}-V{graph_version}, exec-id: {graph_exec_id}" logger.info(f"Starting execution of {log_id}") - for event in event_bus.listen( - user_id=graph_exec.user_id, - graph_id=graph_exec.graph_id, - graph_exec_id=graph_exec.id, + async for event in event_bus.listen( + user_id=user_id, + graph_id=graph_id, + graph_exec_id=graph_exec_id, ): + if event.status not in [ + ExecutionStatus.COMPLETED, + ExecutionStatus.TERMINATED, + ExecutionStatus.FAILED, + ]: + logger.debug( + f"Execution {log_id} received event {event.event_type} with status {event.status}" + ) + continue + if event.event_type == ExecutionEventType.GRAPH_EXEC_UPDATE: - if event.status in [ - ExecutionStatus.COMPLETED, - ExecutionStatus.TERMINATED, - ExecutionStatus.FAILED, - ]: - logger.info(f"Execution {log_id} ended with status {event.status}") - break - else: - continue + # If the graph execution is COMPLETED, TERMINATED, or FAILED, + # we can stop listening for further events. + break logger.debug( f"Execution {log_id} produced input {event.input_data} output {event.output_data}" diff --git a/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py b/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py index 230f3acd88..39c0d4ac54 100644 --- a/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py +++ b/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py @@ -165,7 +165,7 @@ class AIImageGeneratorBlock(Block): }, ) - def _run_client( + async def _run_client( self, credentials: APIKeyCredentials, model_name: str, input_params: dict ): try: @@ -173,7 +173,7 @@ class AIImageGeneratorBlock(Block): client = ReplicateClient(api_token=credentials.api_key.get_secret_value()) # Run the model with input parameters - output = client.run(model_name, input=input_params, wait=False) + output = await client.async_run(model_name, input=input_params, wait=False) # Process output if isinstance(output, list) and len(output) > 0: @@ -195,7 +195,7 @@ class AIImageGeneratorBlock(Block): except Exception as e: raise RuntimeError(f"Unexpected error during model execution: {e}") - def generate_image(self, input_data: Input, credentials: APIKeyCredentials): + async def generate_image(self, input_data: Input, credentials: APIKeyCredentials): try: # Handle style-based prompt modification for models without native style support modified_prompt = input_data.prompt @@ -213,7 +213,7 @@ class AIImageGeneratorBlock(Block): "steps": 40, "cfg_scale": 7.0, } - output = self._run_client( + output = await self._run_client( credentials, "stability-ai/stable-diffusion-3.5-medium", input_params, @@ -231,7 +231,7 @@ class AIImageGeneratorBlock(Block): "output_format": "jpg", # Set to jpg for Flux models "output_quality": 90, } - output = self._run_client( + output = await self._run_client( credentials, "black-forest-labs/flux-1.1-pro", input_params ) return output @@ -246,7 +246,7 @@ class AIImageGeneratorBlock(Block): "output_format": "jpg", "output_quality": 90, } - output = self._run_client( + output = await self._run_client( credentials, "black-forest-labs/flux-1.1-pro-ultra", input_params ) return output @@ -257,7 +257,7 @@ class AIImageGeneratorBlock(Block): "size": SIZE_TO_RECRAFT_DIMENSIONS[input_data.size], "style": input_data.style.value, } - output = self._run_client( + output = await self._run_client( credentials, "recraft-ai/recraft-v3", input_params ) return output @@ -296,9 +296,9 @@ class AIImageGeneratorBlock(Block): style_text = style_map.get(style, "") return f"{style_text} of" if style_text else "" - def run(self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs): + async def run(self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs): try: - url = self.generate_image(input_data, credentials) + url = await self.generate_image(input_data, credentials) if url: yield "image_url", url else: diff --git a/autogpt_platform/backend/backend/blocks/ai_music_generator.py b/autogpt_platform/backend/backend/blocks/ai_music_generator.py index ce9cf45498..b4561bd513 100644 --- a/autogpt_platform/backend/backend/blocks/ai_music_generator.py +++ b/autogpt_platform/backend/backend/blocks/ai_music_generator.py @@ -1,5 +1,5 @@ +import asyncio import logging -import time from enum import Enum from typing import Literal @@ -142,7 +142,7 @@ class AIMusicGeneratorBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: max_retries = 3 @@ -154,7 +154,7 @@ class AIMusicGeneratorBlock(Block): logger.debug( f"[AIMusicGeneratorBlock] - Running model (attempt {attempt + 1})" ) - result = self.run_model( + result = await self.run_model( api_key=credentials.api_key, music_gen_model_version=input_data.music_gen_model_version, prompt=input_data.prompt, @@ -176,13 +176,13 @@ class AIMusicGeneratorBlock(Block): last_error = f"Unexpected error: {str(e)}" logger.error(f"[AIMusicGeneratorBlock] - Error: {last_error}") if attempt < max_retries - 1: - time.sleep(retry_delay) + await asyncio.sleep(retry_delay) continue # If we've exhausted all retries, yield the error yield "error", f"Failed after {max_retries} attempts. Last error: {last_error}" - def run_model( + async def run_model( self, api_key: SecretStr, music_gen_model_version: MusicGenModelVersion, @@ -199,7 +199,7 @@ class AIMusicGeneratorBlock(Block): client = ReplicateClient(api_token=api_key.get_secret_value()) # Run the model with parameters - output = client.run( + output = await client.async_run( "meta/musicgen:671ac645ce5e552cc63a54a2bbff63fcf798043055d2dac5fc9e36a837eedcfb", input={ "prompt": prompt, diff --git a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py index df2b3a2726..c3c4e36472 100644 --- a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py +++ b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py @@ -1,3 +1,4 @@ +import asyncio import logging import time from enum import Enum @@ -13,7 +14,7 @@ from backend.data.model import ( SchemaField, ) from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", @@ -216,29 +217,29 @@ class AIShortformVideoCreatorBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def create_webhook(self): + async def create_webhook(self): url = "https://webhook.site/token" headers = {"Accept": "application/json", "Content-Type": "application/json"} - response = requests.post(url, headers=headers) + response = await Requests().post(url, headers=headers) webhook_data = response.json() return webhook_data["uuid"], f"https://webhook.site/{webhook_data['uuid']}" - def create_video(self, api_key: SecretStr, payload: dict) -> dict: + async def create_video(self, api_key: SecretStr, payload: dict) -> dict: url = "https://www.revid.ai/api/public/v2/render" headers = {"key": api_key.get_secret_value()} - response = requests.post(url, json=payload, headers=headers) + response = await Requests().post(url, json=payload, headers=headers) logger.debug( - f"API Response Status Code: {response.status_code}, Content: {response.text}" + f"API Response Status Code: {response.status}, Content: {response.text}" ) return response.json() - def check_video_status(self, api_key: SecretStr, pid: str) -> dict: + async def check_video_status(self, api_key: SecretStr, pid: str) -> dict: url = f"https://www.revid.ai/api/public/v2/status?pid={pid}" headers = {"key": api_key.get_secret_value()} - response = requests.get(url, headers=headers) + response = await Requests().get(url, headers=headers) return response.json() - def wait_for_video( + async def wait_for_video( self, api_key: SecretStr, pid: str, @@ -247,7 +248,7 @@ class AIShortformVideoCreatorBlock(Block): ) -> str: start_time = time.time() while time.time() - start_time < max_wait_time: - status = self.check_video_status(api_key, pid) + status = await self.check_video_status(api_key, pid) logger.debug(f"Video status: {status}") if status.get("status") == "ready" and "videoUrl" in status: @@ -260,16 +261,16 @@ class AIShortformVideoCreatorBlock(Block): logger.error(f"Video creation failed: {status.get('message')}") raise ValueError(f"Video creation failed: {status.get('message')}") - time.sleep(10) + await asyncio.sleep(10) logger.error("Video creation timed out") raise TimeoutError("Video creation timed out") - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: # Create a new Webhook.site URL - webhook_token, webhook_url = self.create_webhook() + webhook_token, webhook_url = await self.create_webhook() logger.debug(f"Webhook URL: {webhook_url}") audio_url = input_data.background_music.audio_url @@ -306,7 +307,7 @@ class AIShortformVideoCreatorBlock(Block): } logger.debug("Creating video...") - response = self.create_video(credentials.api_key, payload) + response = await self.create_video(credentials.api_key, payload) pid = response.get("pid") if not pid: @@ -318,6 +319,8 @@ class AIShortformVideoCreatorBlock(Block): logger.debug( f"Video created with project ID: {pid}. Waiting for completion..." ) - video_url = self.wait_for_video(credentials.api_key, pid, webhook_token) + video_url = await self.wait_for_video( + credentials.api_key, pid, webhook_token + ) logger.debug(f"Video ready: {video_url}") yield "video_url", video_url diff --git a/autogpt_platform/backend/backend/blocks/apollo/_api.py b/autogpt_platform/backend/backend/blocks/apollo/_api.py index 157235ff0f..dd4e6aa741 100644 --- a/autogpt_platform/backend/backend/blocks/apollo/_api.py +++ b/autogpt_platform/backend/backend/blocks/apollo/_api.py @@ -27,14 +27,15 @@ class ApolloClient: def _get_headers(self) -> dict[str, str]: return {"x-api-key": self.credentials.api_key.get_secret_value()} - def search_people(self, query: SearchPeopleRequest) -> List[Contact]: + async def search_people(self, query: SearchPeopleRequest) -> List[Contact]: """Search for people in Apollo""" - response = self.requests.get( + response = await self.requests.post( f"{self.API_URL}/mixed_people/search", headers=self._get_headers(), - params=query.model_dump(exclude={"credentials", "max_results"}), + json=query.model_dump(exclude={"max_results"}), ) - parsed_response = SearchPeopleResponse(**response.json()) + data = response.json() + parsed_response = SearchPeopleResponse(**data) if parsed_response.pagination.total_entries == 0: return [] @@ -52,27 +53,29 @@ class ApolloClient: and len(parsed_response.people) > 0 ): query.page += 1 - response = self.requests.get( + response = await self.requests.post( f"{self.API_URL}/mixed_people/search", headers=self._get_headers(), - params=query.model_dump(exclude={"credentials", "max_results"}), + json=query.model_dump(exclude={"max_results"}), ) - parsed_response = SearchPeopleResponse(**response.json()) + data = response.json() + parsed_response = SearchPeopleResponse(**data) people.extend(parsed_response.people[: query.max_results - len(people)]) logger.info(f"Found {len(people)} people") return people[: query.max_results] if query.max_results else people - def search_organizations( + async def search_organizations( self, query: SearchOrganizationsRequest ) -> List[Organization]: """Search for organizations in Apollo""" - response = self.requests.get( + response = await self.requests.post( f"{self.API_URL}/mixed_companies/search", headers=self._get_headers(), - params=query.model_dump(exclude={"credentials", "max_results"}), + json=query.model_dump(exclude={"max_results"}), ) - parsed_response = SearchOrganizationsResponse(**response.json()) + data = response.json() + parsed_response = SearchOrganizationsResponse(**data) if parsed_response.pagination.total_entries == 0: return [] @@ -90,12 +93,13 @@ class ApolloClient: and len(parsed_response.organizations) > 0 ): query.page += 1 - response = self.requests.get( + response = await self.requests.post( f"{self.API_URL}/mixed_companies/search", headers=self._get_headers(), - params=query.model_dump(exclude={"credentials", "max_results"}), + json=query.model_dump(exclude={"max_results"}), ) - parsed_response = SearchOrganizationsResponse(**response.json()) + data = response.json() + parsed_response = SearchOrganizationsResponse(**data) organizations.extend( parsed_response.organizations[ : query.max_results - len(organizations) diff --git a/autogpt_platform/backend/backend/blocks/apollo/models.py b/autogpt_platform/backend/backend/blocks/apollo/models.py index 25fbb4d669..f97da43eaf 100644 --- a/autogpt_platform/backend/backend/blocks/apollo/models.py +++ b/autogpt_platform/backend/backend/blocks/apollo/models.py @@ -1,17 +1,31 @@ from enum import Enum from typing import Any, Optional -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel as OriginalBaseModel +from pydantic import ConfigDict from backend.data.model import SchemaField +class BaseModel(OriginalBaseModel): + def model_dump(self, *args, exclude: set[str] | None = None, **kwargs): + if exclude is None: + exclude = set("credentials") + else: + exclude.add("credentials") + + kwargs.setdefault("exclude_none", True) + kwargs.setdefault("exclude_unset", True) + kwargs.setdefault("exclude_defaults", True) + return super().model_dump(*args, exclude=exclude, **kwargs) + + class PrimaryPhone(BaseModel): """A primary phone in Apollo""" - number: str - source: str - sanitized_number: str + number: str = "" + source: str = "" + sanitized_number: str = "" class SenorityLevels(str, Enum): @@ -42,88 +56,88 @@ class ContactEmailStatuses(str, Enum): class RuleConfigStatus(BaseModel): """A rule config status in Apollo""" - _id: str - created_at: str - rule_action_config_id: str - rule_config_id: str - status_cd: str - updated_at: str - id: str - key: str + _id: str = "" + created_at: str = "" + rule_action_config_id: str = "" + rule_config_id: str = "" + status_cd: str = "" + updated_at: str = "" + id: str = "" + key: str = "" class ContactCampaignStatus(BaseModel): """A contact campaign status in Apollo""" - id: str - emailer_campaign_id: str - send_email_from_user_id: str - inactive_reason: str - status: str - added_at: str - added_by_user_id: str - finished_at: str - paused_at: str - auto_unpause_at: str - send_email_from_email_address: str - send_email_from_email_account_id: str - manually_set_unpause: str - failure_reason: str - current_step_id: str - in_response_to_emailer_message_id: str - cc_emails: str - bcc_emails: str - to_emails: str + id: str = "" + emailer_campaign_id: str = "" + send_email_from_user_id: str = "" + inactive_reason: str = "" + status: str = "" + added_at: str = "" + added_by_user_id: str = "" + finished_at: str = "" + paused_at: str = "" + auto_unpause_at: str = "" + send_email_from_email_address: str = "" + send_email_from_email_account_id: str = "" + manually_set_unpause: str = "" + failure_reason: str = "" + current_step_id: str = "" + in_response_to_emailer_message_id: str = "" + cc_emails: str = "" + bcc_emails: str = "" + to_emails: str = "" class Account(BaseModel): """An account in Apollo""" - id: str - name: str - website_url: str - blog_url: str - angellist_url: str - linkedin_url: str - twitter_url: str - facebook_url: str - primary_phone: PrimaryPhone + id: str = "" + name: str = "" + website_url: str = "" + blog_url: str = "" + angellist_url: str = "" + linkedin_url: str = "" + twitter_url: str = "" + facebook_url: str = "" + primary_phone: PrimaryPhone = PrimaryPhone() languages: list[str] - alexa_ranking: int - phone: str - linkedin_uid: str - founded_year: int - publicly_traded_symbol: str - publicly_traded_exchange: str - logo_url: str - chrunchbase_url: str - primary_domain: str - domain: str - team_id: str - organization_id: str - account_stage_id: str - source: str - original_source: str - creator_id: str - owner_id: str - created_at: str - phone_status: str - hubspot_id: str - salesforce_id: str - crm_owner_id: str - parent_account_id: str - sanitized_phone: str + alexa_ranking: int = 0 + phone: str = "" + linkedin_uid: str = "" + founded_year: int = 0 + publicly_traded_symbol: str = "" + publicly_traded_exchange: str = "" + logo_url: str = "" + chrunchbase_url: str = "" + primary_domain: str = "" + domain: str = "" + team_id: str = "" + organization_id: str = "" + account_stage_id: str = "" + source: str = "" + original_source: str = "" + creator_id: str = "" + owner_id: str = "" + created_at: str = "" + phone_status: str = "" + hubspot_id: str = "" + salesforce_id: str = "" + crm_owner_id: str = "" + parent_account_id: str = "" + sanitized_phone: str = "" # no listed type on the API docs - account_playbook_statues: list[Any] - account_rule_config_statuses: list[RuleConfigStatus] - existence_level: str - label_ids: list[str] + account_playbook_statues: list[Any] = [] + account_rule_config_statuses: list[RuleConfigStatus] = [] + existence_level: str = "" + label_ids: list[str] = [] typed_custom_fields: Any custom_field_errors: Any - modality: str - source_display_name: str - salesforce_record_id: str - crm_record_url: str + modality: str = "" + source_display_name: str = "" + salesforce_record_id: str = "" + crm_record_url: str = "" class ContactEmail(BaseModel): @@ -205,7 +219,7 @@ class Pagination(BaseModel): class DialerFlags(BaseModel): """A dialer flags in Apollo""" - country_name: str + country_name: str = "" country_enabled: bool high_risk_calling_enabled: bool potential_high_risk_number: bool diff --git a/autogpt_platform/backend/backend/blocks/apollo/organization.py b/autogpt_platform/backend/backend/blocks/apollo/organization.py index 37537a6461..e21b0ab5d9 100644 --- a/autogpt_platform/backend/backend/blocks/apollo/organization.py +++ b/autogpt_platform/backend/backend/blocks/apollo/organization.py @@ -201,19 +201,17 @@ To find IDs, identify the values for organization_id when you call this endpoint ) @staticmethod - def search_organizations( + async def search_organizations( query: SearchOrganizationsRequest, credentials: ApolloCredentials ) -> list[Organization]: client = ApolloClient(credentials) - return client.search_organizations(query) + return await client.search_organizations(query) - def run( + async def run( self, input_data: Input, *, credentials: ApolloCredentials, **kwargs ) -> BlockOutput: - query = SearchOrganizationsRequest( - **input_data.model_dump(exclude={"credentials"}) - ) - organizations = self.search_organizations(query, credentials) + query = SearchOrganizationsRequest(**input_data.model_dump()) + organizations = await self.search_organizations(query, credentials) for organization in organizations: yield "organization", organization yield "organizations", organizations diff --git a/autogpt_platform/backend/backend/blocks/apollo/people.py b/autogpt_platform/backend/backend/blocks/apollo/people.py index 628bb5dc7c..c6d8620b7d 100644 --- a/autogpt_platform/backend/backend/blocks/apollo/people.py +++ b/autogpt_platform/backend/backend/blocks/apollo/people.py @@ -107,6 +107,7 @@ class SearchPeopleBlock(Block): default_factory=list, ) person: Contact = SchemaField( + title="Person", description="Each found person, one at a time", ) error: str = SchemaField( @@ -373,13 +374,13 @@ class SearchPeopleBlock(Block): ) @staticmethod - def search_people( + async def search_people( query: SearchPeopleRequest, credentials: ApolloCredentials ) -> list[Contact]: client = ApolloClient(credentials) - return client.search_people(query) + return await client.search_people(query) - def run( + async def run( self, input_data: Input, *, @@ -387,8 +388,8 @@ class SearchPeopleBlock(Block): **kwargs, ) -> BlockOutput: - query = SearchPeopleRequest(**input_data.model_dump(exclude={"credentials"})) - people = self.search_people(query, credentials) + query = SearchPeopleRequest(**input_data.model_dump()) + people = await self.search_people(query, credentials) for person in people: yield "person", person yield "people", people diff --git a/autogpt_platform/backend/backend/blocks/basic.py b/autogpt_platform/backend/backend/blocks/basic.py index 2c7edeb0ad..7e52e70f12 100644 --- a/autogpt_platform/backend/backend/blocks/basic.py +++ b/autogpt_platform/backend/backend/blocks/basic.py @@ -30,14 +30,14 @@ class FileStoreBlock(Block): static_output=True, ) - def run( + async def run( self, input_data: Input, *, graph_exec_id: str, **kwargs, ) -> BlockOutput: - file_path = store_media_file( + file_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.file_in, return_content=False, @@ -84,7 +84,7 @@ class StoreValueBlock(Block): static_output=True, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "output", input_data.data or input_data.input @@ -110,7 +110,7 @@ class PrintToConsoleBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "output", input_data.text yield "status", "printed" @@ -151,7 +151,7 @@ class FindInDictionaryBlock(Block): categories={BlockCategory.BASIC}, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: obj = input_data.input key = input_data.key @@ -241,7 +241,7 @@ class AddToDictionaryBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: updated_dict = input_data.dictionary.copy() if input_data.value is not None and input_data.key: @@ -319,7 +319,7 @@ class AddToListBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: entries_added = input_data.entries.copy() if input_data.entry: entries_added.append(input_data.entry) @@ -366,7 +366,7 @@ class FindInListBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: yield "index", input_data.list.index(input_data.value) yield "found", True @@ -396,7 +396,7 @@ class NoteBlock(Block): block_type=BlockType.NOTE, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "output", input_data.text @@ -442,7 +442,7 @@ class CreateDictionaryBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: # The values are already validated by Pydantic schema yield "dictionary", input_data.values @@ -456,6 +456,11 @@ class CreateListBlock(Block): description="A list of values to be combined into a new list.", placeholder="e.g., ['Alice', 25, True]", ) + max_size: int | None = SchemaField( + default=None, + description="Maximum size of the list. If provided, the list will be yielded in chunks of this size.", + advanced=True, + ) class Output(BlockSchema): list: List[Any] = SchemaField( @@ -490,10 +495,11 @@ class CreateListBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: - # The values are already validated by Pydantic schema - yield "list", input_data.values + max_size = input_data.max_size or len(input_data.values) + for i in range(0, len(input_data.values), max_size): + yield "list", input_data.values[i : i + max_size] except Exception as e: yield "error", f"Failed to create list: {str(e)}" @@ -525,7 +531,7 @@ class UniversalTypeConverterBlock(Block): output_schema=UniversalTypeConverterBlock.Output, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: converted_value = convert( input_data.value, diff --git a/autogpt_platform/backend/backend/blocks/block.py b/autogpt_platform/backend/backend/blocks/block.py index 01e8af7238..e1745d3055 100644 --- a/autogpt_platform/backend/backend/blocks/block.py +++ b/autogpt_platform/backend/backend/blocks/block.py @@ -38,7 +38,7 @@ class BlockInstallationBlock(Block): disabled=True, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: code = input_data.code if search := re.search(r"class (\w+)\(Block\):", code): @@ -64,7 +64,7 @@ class BlockInstallationBlock(Block): from backend.util.test import execute_block_test - execute_block_test(block) + await execute_block_test(block) yield "success", "Block installed successfully." except Exception as e: os.remove(file_path) diff --git a/autogpt_platform/backend/backend/blocks/branching.py b/autogpt_platform/backend/backend/blocks/branching.py index a3424d3374..17cfd6d5c1 100644 --- a/autogpt_platform/backend/backend/blocks/branching.py +++ b/autogpt_platform/backend/backend/blocks/branching.py @@ -70,7 +70,7 @@ class ConditionBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: operator = input_data.operator value1 = input_data.value1 @@ -163,7 +163,7 @@ class IfInputMatchesBlock(Block): }, { "input": 10, - "value": None, + "value": "None", "yes_value": "Yes", "no_value": "No", }, @@ -180,7 +180,7 @@ class IfInputMatchesBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: if input_data.input == input_data.value or input_data.input is input_data.value: yield "result", True yield "yes_output", input_data.yes_value diff --git a/autogpt_platform/backend/backend/blocks/code_executor.py b/autogpt_platform/backend/backend/blocks/code_executor.py index 409c02bab0..e25231e90e 100644 --- a/autogpt_platform/backend/backend/blocks/code_executor.py +++ b/autogpt_platform/backend/backend/blocks/code_executor.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Literal -from e2b_code_interpreter import Sandbox +from e2b_code_interpreter import AsyncSandbox from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema @@ -123,7 +123,7 @@ class CodeExecutionBlock(Block): }, ) - def execute_code( + async def execute_code( self, code: str, language: ProgrammingLanguage, @@ -135,21 +135,21 @@ class CodeExecutionBlock(Block): try: sandbox = None if template_id: - sandbox = Sandbox( + sandbox = await AsyncSandbox.create( template=template_id, api_key=api_key, timeout=timeout ) else: - sandbox = Sandbox(api_key=api_key, timeout=timeout) + sandbox = await AsyncSandbox.create(api_key=api_key, timeout=timeout) if not sandbox: raise Exception("Sandbox not created") # Running setup commands for cmd in setup_commands: - sandbox.commands.run(cmd) + await sandbox.commands.run(cmd) # Executing the code - execution = sandbox.run_code( + execution = await sandbox.run_code( code, language=language.value, on_error=lambda e: sandbox.kill(), # Kill the sandbox if there is an error @@ -167,11 +167,11 @@ class CodeExecutionBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - response, stdout_logs, stderr_logs = self.execute_code( + response, stdout_logs, stderr_logs = await self.execute_code( input_data.code, input_data.language, input_data.setup_commands, @@ -278,11 +278,11 @@ class InstantiationBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - sandbox_id, response, stdout_logs, stderr_logs = self.execute_code( + sandbox_id, response, stdout_logs, stderr_logs = await self.execute_code( input_data.setup_code, input_data.language, input_data.setup_commands, @@ -303,7 +303,7 @@ class InstantiationBlock(Block): except Exception as e: yield "error", str(e) - def execute_code( + async def execute_code( self, code: str, language: ProgrammingLanguage, @@ -315,21 +315,21 @@ class InstantiationBlock(Block): try: sandbox = None if template_id: - sandbox = Sandbox( + sandbox = await AsyncSandbox.create( template=template_id, api_key=api_key, timeout=timeout ) else: - sandbox = Sandbox(api_key=api_key, timeout=timeout) + sandbox = await AsyncSandbox.create(api_key=api_key, timeout=timeout) if not sandbox: raise Exception("Sandbox not created") # Running setup commands for cmd in setup_commands: - sandbox.commands.run(cmd) + await sandbox.commands.run(cmd) # Executing the code - execution = sandbox.run_code( + execution = await sandbox.run_code( code, language=language.value, on_error=lambda e: sandbox.kill(), # Kill the sandbox if there is an error @@ -409,7 +409,7 @@ class StepExecutionBlock(Block): }, ) - def execute_step_code( + async def execute_step_code( self, sandbox_id: str, code: str, @@ -417,12 +417,12 @@ class StepExecutionBlock(Block): api_key: str, ): try: - sandbox = Sandbox.connect(sandbox_id=sandbox_id, api_key=api_key) + sandbox = await AsyncSandbox.connect(sandbox_id=sandbox_id, api_key=api_key) if not sandbox: raise Exception("Sandbox not found") # Executing the code - execution = sandbox.run_code(code, language=language.value) + execution = await sandbox.run_code(code, language=language.value) if execution.error: raise Exception(execution.error) @@ -436,11 +436,11 @@ class StepExecutionBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - response, stdout_logs, stderr_logs = self.execute_step_code( + response, stdout_logs, stderr_logs = await self.execute_step_code( input_data.sandbox_id, input_data.step_code, input_data.language, diff --git a/autogpt_platform/backend/backend/blocks/code_extraction_block.py b/autogpt_platform/backend/backend/blocks/code_extraction_block.py index ab1e35aa5d..33bf225bfd 100644 --- a/autogpt_platform/backend/backend/blocks/code_extraction_block.py +++ b/autogpt_platform/backend/backend/blocks/code_extraction_block.py @@ -49,7 +49,7 @@ class CodeExtractionBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # List of supported programming languages with mapped aliases language_aliases = { "html": ["html", "htm"], diff --git a/autogpt_platform/backend/backend/blocks/compass/triggers.py b/autogpt_platform/backend/backend/blocks/compass/triggers.py index 662e39ecea..6eac52ce53 100644 --- a/autogpt_platform/backend/backend/blocks/compass/triggers.py +++ b/autogpt_platform/backend/backend/blocks/compass/triggers.py @@ -56,5 +56,5 @@ class CompassAITriggerBlock(Block): # ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "transcription", input_data.payload.transcription diff --git a/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py b/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py index 13f9e39779..ddbcf07876 100644 --- a/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py +++ b/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py @@ -30,7 +30,7 @@ class WordCharacterCountBlock(Block): test_output=[("word_count", 4), ("character_count", 19)], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: text = input_data.text word_count = len(text.split()) diff --git a/autogpt_platform/backend/backend/blocks/csv.py b/autogpt_platform/backend/backend/blocks/csv.py index 3cc3575b31..f69eeff4a9 100644 --- a/autogpt_platform/backend/backend/blocks/csv.py +++ b/autogpt_platform/backend/backend/blocks/csv.py @@ -69,7 +69,7 @@ class ReadCsvBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: import csv from io import StringIO diff --git a/autogpt_platform/backend/backend/blocks/decoder_block.py b/autogpt_platform/backend/backend/blocks/decoder_block.py index 033cdfb0b3..754d79b068 100644 --- a/autogpt_platform/backend/backend/blocks/decoder_block.py +++ b/autogpt_platform/backend/backend/blocks/decoder_block.py @@ -34,6 +34,6 @@ This is a "quoted" string.""", ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: decoded_text = codecs.decode(input_data.text, "unicode_escape") yield "decoded_text", decoded_text diff --git a/autogpt_platform/backend/backend/blocks/discord.py b/autogpt_platform/backend/backend/blocks/discord.py index 08ba8af074..91aba5f414 100644 --- a/autogpt_platform/backend/backend/blocks/discord.py +++ b/autogpt_platform/backend/backend/blocks/discord.py @@ -1,4 +1,3 @@ -import asyncio from typing import Literal import aiohttp @@ -74,7 +73,11 @@ class ReadDiscordMessagesBlock(Block): ("username", "test_user"), ], test_mock={ - "run_bot": lambda token: asyncio.Future() # Create a Future object for mocking + "run_bot": lambda token: { + "output_data": "Hello!\n\nFile from user: example.txt\nContent: This is the content of the file.", + "channel_name": "general", + "username": "test_user", + } }, ) @@ -106,37 +109,24 @@ class ReadDiscordMessagesBlock(Block): if attachment.filename.endswith((".txt", ".py")): async with aiohttp.ClientSession() as session: async with session.get(attachment.url) as response: - file_content = await response.text() + file_content = response.text() self.output_data += f"\n\nFile from user: {attachment.filename}\nContent: {file_content}" await client.close() await client.start(token.get_secret_value()) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - while True: - for output_name, output_value in self.__run(input_data, credentials): - yield output_name, output_value - break + async for output_name, output_value in self.__run(input_data, credentials): + yield output_name, output_value - def __run(self, input_data: Input, credentials: APIKeyCredentials) -> BlockOutput: + async def __run( + self, input_data: Input, credentials: APIKeyCredentials + ) -> BlockOutput: try: - loop = asyncio.get_event_loop() - future = self.run_bot(credentials.api_key) - - # If it's a Future (mock), set the result - if isinstance(future, asyncio.Future): - future.set_result( - { - "output_data": "Hello!\n\nFile from user: example.txt\nContent: This is the content of the file.", - "channel_name": "general", - "username": "test_user", - } - ) - - result = loop.run_until_complete(future) + result = await self.run_bot(credentials.api_key) # For testing purposes, use the mocked result if isinstance(result, dict): @@ -190,7 +180,7 @@ class SendDiscordMessageBlock(Block): }, test_output=[("status", "Message sent")], test_mock={ - "send_message": lambda token, channel_name, message_content: asyncio.Future() + "send_message": lambda token, channel_name, message_content: "Message sent" }, test_credentials=TEST_CREDENTIALS, ) @@ -222,23 +212,16 @@ class SendDiscordMessageBlock(Block): """Splits a message into chunks not exceeding the Discord limit.""" return [message[i : i + limit] for i in range(0, len(message), limit)] - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - loop = asyncio.get_event_loop() - future = self.send_message( + result = await self.send_message( credentials.api_key.get_secret_value(), input_data.channel_name, input_data.message_content, ) - # If it's a Future (mock), set the result - if isinstance(future, asyncio.Future): - future.set_result("Message sent") - - result = loop.run_until_complete(future) - # For testing purposes, use the mocked result if isinstance(result, str): self.output_data = result diff --git a/autogpt_platform/backend/backend/blocks/email_block.py b/autogpt_platform/backend/backend/blocks/email_block.py index 4159886cee..3738bf0de8 100644 --- a/autogpt_platform/backend/backend/blocks/email_block.py +++ b/autogpt_platform/backend/backend/blocks/email_block.py @@ -121,7 +121,7 @@ class SendEmailBlock(Block): return "Email sent successfully" - def run( + async def run( self, input_data: Input, *, credentials: SMTPCredentials, **kwargs ) -> BlockOutput: yield "status", self.send_email( diff --git a/autogpt_platform/backend/backend/blocks/exa/contents.py b/autogpt_platform/backend/backend/blocks/exa/contents.py index 7210af433d..920a5ac82f 100644 --- a/autogpt_platform/backend/backend/blocks/exa/contents.py +++ b/autogpt_platform/backend/backend/blocks/exa/contents.py @@ -9,7 +9,7 @@ from backend.blocks.exa._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class ContentRetrievalSettings(BaseModel): @@ -62,7 +62,7 @@ class ExaContentsBlock(Block): output_schema=ExaContentsBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: ExaCredentials, **kwargs ) -> BlockOutput: url = "https://api.exa.ai/contents" @@ -79,8 +79,7 @@ class ExaContentsBlock(Block): } try: - response = requests.post(url, headers=headers, json=payload) - response.raise_for_status() + response = await Requests().post(url, headers=headers, json=payload) data = response.json() yield "results", data.get("results", []) except Exception as e: diff --git a/autogpt_platform/backend/backend/blocks/exa/search.py b/autogpt_platform/backend/backend/blocks/exa/search.py index 5915455a56..1f4d0005ce 100644 --- a/autogpt_platform/backend/backend/blocks/exa/search.py +++ b/autogpt_platform/backend/backend/blocks/exa/search.py @@ -9,7 +9,7 @@ from backend.blocks.exa._auth import ( from backend.blocks.exa.helpers import ContentSettings from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class ExaSearchBlock(Block): @@ -91,7 +91,7 @@ class ExaSearchBlock(Block): output_schema=ExaSearchBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: ExaCredentials, **kwargs ) -> BlockOutput: url = "https://api.exa.ai/search" @@ -136,8 +136,7 @@ class ExaSearchBlock(Block): payload[api_field] = value try: - response = requests.post(url, headers=headers, json=payload) - response.raise_for_status() + response = await Requests().post(url, headers=headers, json=payload) data = response.json() # Extract just the results array from the response yield "results", data.get("results", []) diff --git a/autogpt_platform/backend/backend/blocks/exa/similar.py b/autogpt_platform/backend/backend/blocks/exa/similar.py index 036d26a481..36dc23c5c5 100644 --- a/autogpt_platform/backend/backend/blocks/exa/similar.py +++ b/autogpt_platform/backend/backend/blocks/exa/similar.py @@ -8,7 +8,7 @@ from backend.blocks.exa._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests from .helpers import ContentSettings @@ -78,7 +78,7 @@ class ExaFindSimilarBlock(Block): output_schema=ExaFindSimilarBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: ExaCredentials, **kwargs ) -> BlockOutput: url = "https://api.exa.ai/findSimilar" @@ -120,8 +120,7 @@ class ExaFindSimilarBlock(Block): payload[api_field] = value.strftime("%Y-%m-%dT%H:%M:%S.000Z") try: - response = requests.post(url, headers=headers, json=payload) - response.raise_for_status() + response = await Requests().post(url, headers=headers, json=payload) data = response.json() yield "results", data.get("results", []) except Exception as e: diff --git a/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py b/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py index fc2152e0ee..2e795f0d78 100644 --- a/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py +++ b/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py @@ -1,10 +1,8 @@ +import asyncio import logging -import time from enum import Enum from typing import Any -import httpx - from backend.blocks.fal._auth import ( TEST_CREDENTIALS, TEST_CREDENTIALS_INPUT, @@ -14,6 +12,7 @@ from backend.blocks.fal._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField +from backend.util.request import ClientResponseError, Requests logger = logging.getLogger(__name__) @@ -66,35 +65,37 @@ class AIVideoGeneratorBlock(Block): ) def _get_headers(self, api_key: str) -> dict[str, str]: - """Get headers for FAL API requests.""" + """Get headers for FAL API Requests.""" return { "Authorization": f"Key {api_key}", "Content-Type": "application/json", } - def _submit_request( + async def _submit_request( self, url: str, headers: dict[str, str], data: dict[str, Any] ) -> dict[str, Any]: """Submit a request to the FAL API.""" try: - response = httpx.post(url, headers=headers, json=data) - response.raise_for_status() + response = await Requests().post(url, headers=headers, json=data) return response.json() - except httpx.HTTPError as e: + except ClientResponseError as e: logger.error(f"FAL API request failed: {str(e)}") raise RuntimeError(f"Failed to submit request: {str(e)}") - def _poll_status(self, status_url: str, headers: dict[str, str]) -> dict[str, Any]: + async def _poll_status( + self, status_url: str, headers: dict[str, str] + ) -> dict[str, Any]: """Poll the status endpoint until completion or failure.""" try: - response = httpx.get(status_url, headers=headers) - response.raise_for_status() + response = await Requests().get(status_url, headers=headers) return response.json() - except httpx.HTTPError as e: + except ClientResponseError as e: logger.error(f"Failed to get status: {str(e)}") raise RuntimeError(f"Failed to get status: {str(e)}") - def generate_video(self, input_data: Input, credentials: FalCredentials) -> str: + async def generate_video( + self, input_data: Input, credentials: FalCredentials + ) -> str: """Generate video using the specified FAL model.""" base_url = "https://queue.fal.run" api_key = credentials.api_key.get_secret_value() @@ -110,8 +111,9 @@ class AIVideoGeneratorBlock(Block): try: # Submit request to queue - submit_response = httpx.post(submit_url, headers=headers, json=submit_data) - submit_response.raise_for_status() + submit_response = await Requests().post( + submit_url, headers=headers, json=submit_data + ) request_data = submit_response.json() # Get request_id and urls from initial response @@ -122,14 +124,23 @@ class AIVideoGeneratorBlock(Block): if not all([request_id, status_url, result_url]): raise ValueError("Missing required data in submission response") + # Ensure status_url is a string + if not isinstance(status_url, str): + raise ValueError("Invalid status URL format") + + # Ensure result_url is a string + if not isinstance(result_url, str): + raise ValueError("Invalid result URL format") + # Poll for status with exponential backoff max_attempts = 30 attempt = 0 base_wait_time = 5 while attempt < max_attempts: - status_response = httpx.get(f"{status_url}?logs=1", headers=headers) - status_response.raise_for_status() + status_response = await Requests().get( + f"{status_url}?logs=1", headers=headers + ) status_data = status_response.json() # Process new logs only @@ -152,8 +163,7 @@ class AIVideoGeneratorBlock(Block): status = status_data.get("status") if status == "COMPLETED": # Get the final result - result_response = httpx.get(result_url, headers=headers) - result_response.raise_for_status() + result_response = await Requests().get(result_url, headers=headers) result_data = result_response.json() if "video" not in result_data or not isinstance( @@ -162,8 +172,8 @@ class AIVideoGeneratorBlock(Block): raise ValueError("Invalid response format - missing video data") video_url = result_data["video"].get("url") - if not video_url: - raise ValueError("No video URL in response") + if not video_url or not isinstance(video_url, str): + raise ValueError("No valid video URL in response") return video_url @@ -183,19 +193,19 @@ class AIVideoGeneratorBlock(Block): logger.info(f"[FAL Generation] Status: Unknown status: {status}") wait_time = min(base_wait_time * (2**attempt), 60) # Cap at 60 seconds - time.sleep(wait_time) + await asyncio.sleep(wait_time) attempt += 1 raise RuntimeError("Maximum polling attempts reached") - except httpx.HTTPError as e: + except ClientResponseError as e: raise RuntimeError(f"API request failed: {str(e)}") - def run( + async def run( self, input_data: Input, *, credentials: FalCredentials, **kwargs ) -> BlockOutput: try: - video_url = self.generate_video(input_data, credentials) + video_url = await self.generate_video(input_data, credentials) yield "video_url", video_url except Exception as e: error_message = str(e) diff --git a/autogpt_platform/backend/backend/blocks/flux_kontext.py b/autogpt_platform/backend/backend/blocks/flux_kontext.py index 1cff9dbba2..f391b41939 100644 --- a/autogpt_platform/backend/backend/blocks/flux_kontext.py +++ b/autogpt_platform/backend/backend/blocks/flux_kontext.py @@ -123,14 +123,14 @@ class AIImageEditorBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs, ) -> BlockOutput: - result = self.run_model( + result = await self.run_model( api_key=credentials.api_key, model_name=input_data.model.api_name, prompt=input_data.prompt, @@ -140,7 +140,7 @@ class AIImageEditorBlock(Block): ) yield "output_image", result - def run_model( + async def run_model( self, api_key: SecretStr, model_name: str, @@ -157,7 +157,7 @@ class AIImageEditorBlock(Block): **({"seed": seed} if seed is not None else {}), } - output: FileOutput | list[FileOutput] = client.run( # type: ignore + output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore model_name, input=input_params, wait=False, diff --git a/autogpt_platform/backend/backend/blocks/generic_webhook/triggers.py b/autogpt_platform/backend/backend/blocks/generic_webhook/triggers.py index 66c106b0c5..66660ac57d 100644 --- a/autogpt_platform/backend/backend/blocks/generic_webhook/triggers.py +++ b/autogpt_platform/backend/backend/blocks/generic_webhook/triggers.py @@ -46,6 +46,6 @@ class GenericWebhookTriggerBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "constants", input_data.constants yield "payload", input_data.payload diff --git a/autogpt_platform/backend/backend/blocks/github/checks.py b/autogpt_platform/backend/backend/blocks/github/checks.py index 070b5179e8..9b9aecdf07 100644 --- a/autogpt_platform/backend/backend/blocks/github/checks.py +++ b/autogpt_platform/backend/backend/blocks/github/checks.py @@ -129,7 +129,7 @@ class GithubCreateCheckRunBlock(Block): ) @staticmethod - def create_check_run( + async def create_check_run( credentials: GithubCredentials, repo_url: str, name: str, @@ -172,7 +172,7 @@ class GithubCreateCheckRunBlock(Block): data.output = output_data check_runs_url = f"{repo_url}/check-runs" - response = api.post( + response = await api.post( check_runs_url, data=data.model_dump_json(exclude_none=True) ) result = response.json() @@ -183,7 +183,7 @@ class GithubCreateCheckRunBlock(Block): "status": result["status"], } - def run( + async def run( self, input_data: Input, *, @@ -191,7 +191,7 @@ class GithubCreateCheckRunBlock(Block): **kwargs, ) -> BlockOutput: try: - result = self.create_check_run( + result = await self.create_check_run( credentials=credentials, repo_url=input_data.repo_url, name=input_data.name, @@ -292,7 +292,7 @@ class GithubUpdateCheckRunBlock(Block): ) @staticmethod - def update_check_run( + async def update_check_run( credentials: GithubCredentials, repo_url: str, check_run_id: int, @@ -325,7 +325,7 @@ class GithubUpdateCheckRunBlock(Block): data.output = output_data check_run_url = f"{repo_url}/check-runs/{check_run_id}" - response = api.patch( + response = await api.patch( check_run_url, data=data.model_dump_json(exclude_none=True) ) result = response.json() @@ -337,7 +337,7 @@ class GithubUpdateCheckRunBlock(Block): "conclusion": result.get("conclusion"), } - def run( + async def run( self, input_data: Input, *, @@ -345,7 +345,7 @@ class GithubUpdateCheckRunBlock(Block): **kwargs, ) -> BlockOutput: try: - result = self.update_check_run( + result = await self.update_check_run( credentials=credentials, repo_url=input_data.repo_url, check_run_id=input_data.check_run_id, diff --git a/autogpt_platform/backend/backend/blocks/github/issues.py b/autogpt_platform/backend/backend/blocks/github/issues.py index e62821a36e..42c027c493 100644 --- a/autogpt_platform/backend/backend/blocks/github/issues.py +++ b/autogpt_platform/backend/backend/blocks/github/issues.py @@ -80,7 +80,7 @@ class GithubCommentBlock(Block): ) @staticmethod - def post_comment( + async def post_comment( credentials: GithubCredentials, issue_url: str, body_text: str ) -> tuple[int, str]: api = get_api(credentials) @@ -88,18 +88,18 @@ class GithubCommentBlock(Block): if "pull" in issue_url: issue_url = issue_url.replace("pull", "issues") comments_url = issue_url + "/comments" - response = api.post(comments_url, json=data) + response = await api.post(comments_url, json=data) comment = response.json() return comment["id"], comment["html_url"] - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - id, url = self.post_comment( + id, url = await self.post_comment( credentials, input_data.issue_url, input_data.comment, @@ -171,7 +171,7 @@ class GithubUpdateCommentBlock(Block): ) @staticmethod - def update_comment( + async def update_comment( credentials: GithubCredentials, comment_url: str, body_text: str ) -> tuple[int, str]: api = get_api(credentials, convert_urls=False) @@ -179,11 +179,11 @@ class GithubUpdateCommentBlock(Block): url = convert_comment_url_to_api_endpoint(comment_url) logger.info(url) - response = api.patch(url, json=data) + response = await api.patch(url, json=data) comment = response.json() return comment["id"], comment["html_url"] - def run( + async def run( self, input_data: Input, *, @@ -209,7 +209,7 @@ class GithubUpdateCommentBlock(Block): raise ValueError( "Must provide either comment_url or comment_id and issue_url" ) - id, url = self.update_comment( + id, url = await self.update_comment( credentials, input_data.comment_url, input_data.comment, @@ -288,7 +288,7 @@ class GithubListCommentsBlock(Block): ) @staticmethod - def list_comments( + async def list_comments( credentials: GithubCredentials, issue_url: str ) -> list[Output.CommentItem]: parsed_url = urlparse(issue_url) @@ -305,7 +305,7 @@ class GithubListCommentsBlock(Block): # Set convert_urls=False since we're already providing an API URL api = get_api(credentials, convert_urls=False) - response = api.get(api_url) + response = await api.get(api_url) comments = response.json() parsed_comments: list[GithubListCommentsBlock.Output.CommentItem] = [ { @@ -318,18 +318,19 @@ class GithubListCommentsBlock(Block): ] return parsed_comments - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - comments = self.list_comments( + comments = await self.list_comments( credentials, input_data.issue_url, ) - yield from (("comment", comment) for comment in comments) + for comment in comments: + yield "comment", comment yield "comments", comments @@ -381,24 +382,24 @@ class GithubMakeIssueBlock(Block): ) @staticmethod - def create_issue( + async def create_issue( credentials: GithubCredentials, repo_url: str, title: str, body: str ) -> tuple[int, str]: api = get_api(credentials) data = {"title": title, "body": body} issues_url = repo_url + "/issues" - response = api.post(issues_url, json=data) + response = await api.post(issues_url, json=data) issue = response.json() return issue["number"], issue["html_url"] - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - number, url = self.create_issue( + number, url = await self.create_issue( credentials, input_data.repo_url, input_data.title, @@ -451,25 +452,25 @@ class GithubReadIssueBlock(Block): ) @staticmethod - def read_issue( + async def read_issue( credentials: GithubCredentials, issue_url: str ) -> tuple[str, str, str]: api = get_api(credentials) - response = api.get(issue_url) + response = await api.get(issue_url) data = response.json() title = data.get("title", "No title found") body = data.get("body", "No body content found") user = data.get("user", {}).get("login", "No user found") return title, body, user - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - title, body, user = self.read_issue( + title, body, user = await self.read_issue( credentials, input_data.issue_url, ) @@ -531,30 +532,30 @@ class GithubListIssuesBlock(Block): ) @staticmethod - def list_issues( + async def list_issues( credentials: GithubCredentials, repo_url: str ) -> list[Output.IssueItem]: api = get_api(credentials) issues_url = repo_url + "/issues" - response = api.get(issues_url) + response = await api.get(issues_url) data = response.json() issues: list[GithubListIssuesBlock.Output.IssueItem] = [ {"title": issue["title"], "url": issue["html_url"]} for issue in data ] return issues - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - issues = self.list_issues( + for issue in await self.list_issues( credentials, input_data.repo_url, - ) - yield from (("issue", issue) for issue in issues) + ): + yield "issue", issue class GithubAddLabelBlock(Block): @@ -593,21 +594,23 @@ class GithubAddLabelBlock(Block): ) @staticmethod - def add_label(credentials: GithubCredentials, issue_url: str, label: str) -> str: + async def add_label( + credentials: GithubCredentials, issue_url: str, label: str + ) -> str: api = get_api(credentials) data = {"labels": [label]} labels_url = issue_url + "/labels" - api.post(labels_url, json=data) + await api.post(labels_url, json=data) return "Label added successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.add_label( + status = await self.add_label( credentials, input_data.issue_url, input_data.label, @@ -653,20 +656,22 @@ class GithubRemoveLabelBlock(Block): ) @staticmethod - def remove_label(credentials: GithubCredentials, issue_url: str, label: str) -> str: + async def remove_label( + credentials: GithubCredentials, issue_url: str, label: str + ) -> str: api = get_api(credentials) label_url = issue_url + f"/labels/{label}" - api.delete(label_url) + await api.delete(label_url) return "Label removed successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.remove_label( + status = await self.remove_label( credentials, input_data.issue_url, input_data.label, @@ -714,7 +719,7 @@ class GithubAssignIssueBlock(Block): ) @staticmethod - def assign_issue( + async def assign_issue( credentials: GithubCredentials, issue_url: str, assignee: str, @@ -722,17 +727,17 @@ class GithubAssignIssueBlock(Block): api = get_api(credentials) assignees_url = issue_url + "/assignees" data = {"assignees": [assignee]} - api.post(assignees_url, json=data) + await api.post(assignees_url, json=data) return "Issue assigned successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.assign_issue( + status = await self.assign_issue( credentials, input_data.issue_url, input_data.assignee, @@ -780,7 +785,7 @@ class GithubUnassignIssueBlock(Block): ) @staticmethod - def unassign_issue( + async def unassign_issue( credentials: GithubCredentials, issue_url: str, assignee: str, @@ -788,17 +793,17 @@ class GithubUnassignIssueBlock(Block): api = get_api(credentials) assignees_url = issue_url + "/assignees" data = {"assignees": [assignee]} - api.delete(assignees_url, json=data) + await api.delete(assignees_url, json=data) return "Issue unassigned successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.unassign_issue( + status = await self.unassign_issue( credentials, input_data.issue_url, input_data.assignee, diff --git a/autogpt_platform/backend/backend/blocks/github/pull_requests.py b/autogpt_platform/backend/backend/blocks/github/pull_requests.py index b29db0ff34..dbb940217c 100644 --- a/autogpt_platform/backend/backend/blocks/github/pull_requests.py +++ b/autogpt_platform/backend/backend/blocks/github/pull_requests.py @@ -65,28 +65,31 @@ class GithubListPullRequestsBlock(Block): ) @staticmethod - def list_prs(credentials: GithubCredentials, repo_url: str) -> list[Output.PRItem]: + async def list_prs( + credentials: GithubCredentials, repo_url: str + ) -> list[Output.PRItem]: api = get_api(credentials) pulls_url = repo_url + "/pulls" - response = api.get(pulls_url) + response = await api.get(pulls_url) data = response.json() pull_requests: list[GithubListPullRequestsBlock.Output.PRItem] = [ {"title": pr["title"], "url": pr["html_url"]} for pr in data ] return pull_requests - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - pull_requests = self.list_prs( + pull_requests = await self.list_prs( credentials, input_data.repo_url, ) - yield from (("pull_request", pr) for pr in pull_requests) + for pr in pull_requests: + yield "pull_request", pr class GithubMakePullRequestBlock(Block): @@ -153,7 +156,7 @@ class GithubMakePullRequestBlock(Block): ) @staticmethod - def create_pr( + async def create_pr( credentials: GithubCredentials, repo_url: str, title: str, @@ -164,11 +167,11 @@ class GithubMakePullRequestBlock(Block): api = get_api(credentials) pulls_url = repo_url + "/pulls" data = {"title": title, "body": body, "head": head, "base": base} - response = api.post(pulls_url, json=data) + response = await api.post(pulls_url, json=data) pr_data = response.json() return pr_data["number"], pr_data["html_url"] - def run( + async def run( self, input_data: Input, *, @@ -176,7 +179,7 @@ class GithubMakePullRequestBlock(Block): **kwargs, ) -> BlockOutput: try: - number, url = self.create_pr( + number, url = await self.create_pr( credentials, input_data.repo_url, input_data.title, @@ -242,39 +245,39 @@ class GithubReadPullRequestBlock(Block): ) @staticmethod - def read_pr(credentials: GithubCredentials, pr_url: str) -> tuple[str, str, str]: + async def read_pr( + credentials: GithubCredentials, pr_url: str + ) -> tuple[str, str, str]: api = get_api(credentials) - # Adjust the URL to access the issue endpoint for PR metadata issue_url = pr_url.replace("/pull/", "/issues/") - response = api.get(issue_url) + response = await api.get(issue_url) data = response.json() title = data.get("title", "No title found") body = data.get("body", "No body content found") - author = data.get("user", {}).get("login", "No user found") + author = data.get("user", {}).get("login", "Unknown author") return title, body, author @staticmethod - def read_pr_changes(credentials: GithubCredentials, pr_url: str) -> str: + async def read_pr_changes(credentials: GithubCredentials, pr_url: str) -> str: api = get_api(credentials) files_url = prepare_pr_api_url(pr_url=pr_url, path="files") - response = api.get(files_url) + response = await api.get(files_url) files = response.json() changes = [] for file in files: - filename = file.get("filename") - patch = file.get("patch") - if filename and patch: - changes.append(f"File: {filename}\n{patch}") - return "\n\n".join(changes) + filename = file.get("filename", "") + status = file.get("status", "") + changes.append(f"{filename}: {status}") + return "\n".join(changes) - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - title, body, author = self.read_pr( + title, body, author = await self.read_pr( credentials, input_data.pr_url, ) @@ -283,7 +286,7 @@ class GithubReadPullRequestBlock(Block): yield "author", author if input_data.include_pr_changes: - changes = self.read_pr_changes( + changes = await self.read_pr_changes( credentials, input_data.pr_url, ) @@ -330,16 +333,16 @@ class GithubAssignPRReviewerBlock(Block): ) @staticmethod - def assign_reviewer( + async def assign_reviewer( credentials: GithubCredentials, pr_url: str, reviewer: str ) -> str: api = get_api(credentials) reviewers_url = prepare_pr_api_url(pr_url=pr_url, path="requested_reviewers") data = {"reviewers": [reviewer]} - api.post(reviewers_url, json=data) + await api.post(reviewers_url, json=data) return "Reviewer assigned successfully" - def run( + async def run( self, input_data: Input, *, @@ -347,7 +350,7 @@ class GithubAssignPRReviewerBlock(Block): **kwargs, ) -> BlockOutput: try: - status = self.assign_reviewer( + status = await self.assign_reviewer( credentials, input_data.pr_url, input_data.reviewer, @@ -397,16 +400,16 @@ class GithubUnassignPRReviewerBlock(Block): ) @staticmethod - def unassign_reviewer( + async def unassign_reviewer( credentials: GithubCredentials, pr_url: str, reviewer: str ) -> str: api = get_api(credentials) reviewers_url = prepare_pr_api_url(pr_url=pr_url, path="requested_reviewers") data = {"reviewers": [reviewer]} - api.delete(reviewers_url, json=data) + await api.delete(reviewers_url, json=data) return "Reviewer unassigned successfully" - def run( + async def run( self, input_data: Input, *, @@ -414,7 +417,7 @@ class GithubUnassignPRReviewerBlock(Block): **kwargs, ) -> BlockOutput: try: - status = self.unassign_reviewer( + status = await self.unassign_reviewer( credentials, input_data.pr_url, input_data.reviewer, @@ -477,12 +480,12 @@ class GithubListPRReviewersBlock(Block): ) @staticmethod - def list_reviewers( + async def list_reviewers( credentials: GithubCredentials, pr_url: str ) -> list[Output.ReviewerItem]: api = get_api(credentials) reviewers_url = prepare_pr_api_url(pr_url=pr_url, path="requested_reviewers") - response = api.get(reviewers_url) + response = await api.get(reviewers_url) data = response.json() reviewers: list[GithubListPRReviewersBlock.Output.ReviewerItem] = [ {"username": reviewer["login"], "url": reviewer["html_url"]} @@ -490,18 +493,18 @@ class GithubListPRReviewersBlock(Block): ] return reviewers - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - reviewers = self.list_reviewers( + for reviewer in await self.list_reviewers( credentials, input_data.pr_url, - ) - yield from (("reviewer", reviewer) for reviewer in reviewers) + ): + yield "reviewer", reviewer def prepare_pr_api_url(pr_url: str, path: str) -> str: diff --git a/autogpt_platform/backend/backend/blocks/github/repo.py b/autogpt_platform/backend/backend/blocks/github/repo.py index 82bef9475b..f44cd95e1a 100644 --- a/autogpt_platform/backend/backend/blocks/github/repo.py +++ b/autogpt_platform/backend/backend/blocks/github/repo.py @@ -65,12 +65,12 @@ class GithubListTagsBlock(Block): ) @staticmethod - def list_tags( + async def list_tags( credentials: GithubCredentials, repo_url: str ) -> list[Output.TagItem]: api = get_api(credentials) tags_url = repo_url + "/tags" - response = api.get(tags_url) + response = await api.get(tags_url) data = response.json() repo_path = repo_url.replace("https://github.com/", "") tags: list[GithubListTagsBlock.Output.TagItem] = [ @@ -82,18 +82,19 @@ class GithubListTagsBlock(Block): ] return tags - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - tags = self.list_tags( + tags = await self.list_tags( credentials, input_data.repo_url, ) - yield from (("tag", tag) for tag in tags) + for tag in tags: + yield "tag", tag class GithubListBranchesBlock(Block): @@ -147,12 +148,12 @@ class GithubListBranchesBlock(Block): ) @staticmethod - def list_branches( + async def list_branches( credentials: GithubCredentials, repo_url: str ) -> list[Output.BranchItem]: api = get_api(credentials) branches_url = repo_url + "/branches" - response = api.get(branches_url) + response = await api.get(branches_url) data = response.json() repo_path = repo_url.replace("https://github.com/", "") branches: list[GithubListBranchesBlock.Output.BranchItem] = [ @@ -164,18 +165,19 @@ class GithubListBranchesBlock(Block): ] return branches - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - branches = self.list_branches( + branches = await self.list_branches( credentials, input_data.repo_url, ) - yield from (("branch", branch) for branch in branches) + for branch in branches: + yield "branch", branch class GithubListDiscussionsBlock(Block): @@ -234,7 +236,7 @@ class GithubListDiscussionsBlock(Block): ) @staticmethod - def list_discussions( + async def list_discussions( credentials: GithubCredentials, repo_url: str, num_discussions: int ) -> list[Output.DiscussionItem]: api = get_api(credentials) @@ -254,7 +256,7 @@ class GithubListDiscussionsBlock(Block): } """ variables = {"owner": owner, "repo": repo, "num": num_discussions} - response = api.post( + response = await api.post( "https://api.github.com/graphql", json={"query": query, "variables": variables}, ) @@ -265,17 +267,20 @@ class GithubListDiscussionsBlock(Block): ] return discussions - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - discussions = self.list_discussions( - credentials, input_data.repo_url, input_data.num_discussions + discussions = await self.list_discussions( + credentials, + input_data.repo_url, + input_data.num_discussions, ) - yield from (("discussion", discussion) for discussion in discussions) + for discussion in discussions: + yield "discussion", discussion class GithubListReleasesBlock(Block): @@ -329,30 +334,31 @@ class GithubListReleasesBlock(Block): ) @staticmethod - def list_releases( + async def list_releases( credentials: GithubCredentials, repo_url: str ) -> list[Output.ReleaseItem]: api = get_api(credentials) releases_url = repo_url + "/releases" - response = api.get(releases_url) + response = await api.get(releases_url) data = response.json() releases: list[GithubListReleasesBlock.Output.ReleaseItem] = [ {"name": release["name"], "url": release["html_url"]} for release in data ] return releases - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - releases = self.list_releases( + releases = await self.list_releases( credentials, input_data.repo_url, ) - yield from (("release", release) for release in releases) + for release in releases: + yield "release", release class GithubReadFileBlock(Block): @@ -405,40 +411,40 @@ class GithubReadFileBlock(Block): ) @staticmethod - def read_file( + async def read_file( credentials: GithubCredentials, repo_url: str, file_path: str, branch: str ) -> tuple[str, int]: api = get_api(credentials) content_url = repo_url + f"/contents/{file_path}?ref={branch}" - response = api.get(content_url) - content = response.json() + response = await api.get(content_url) + data = response.json() - if isinstance(content, list): + if isinstance(data, list): # Multiple entries of different types exist at this path - if not (file := next((f for f in content if f["type"] == "file"), None)): + if not (file := next((f for f in data if f["type"] == "file"), None)): raise TypeError("Not a file") - content = file + data = file - if content["type"] != "file": + if data["type"] != "file": raise TypeError("Not a file") - return content["content"], content["size"] + return data["content"], data["size"] - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - raw_content, size = self.read_file( + content, size = await self.read_file( credentials, input_data.repo_url, - input_data.file_path.lstrip("/"), + input_data.file_path, input_data.branch, ) - yield "raw_content", raw_content - yield "text_content", base64.b64decode(raw_content).decode("utf-8") + yield "raw_content", content + yield "text_content", base64.b64decode(content).decode("utf-8") yield "size", size @@ -515,52 +521,55 @@ class GithubReadFolderBlock(Block): ) @staticmethod - def read_folder( + async def read_folder( credentials: GithubCredentials, repo_url: str, folder_path: str, branch: str ) -> tuple[list[Output.FileEntry], list[Output.DirEntry]]: api = get_api(credentials) contents_url = repo_url + f"/contents/{folder_path}?ref={branch}" - response = api.get(contents_url) - content = response.json() + response = await api.get(contents_url) + data = response.json() - if not isinstance(content, list): + if not isinstance(data, list): raise TypeError("Not a folder") - files = [ + files: list[GithubReadFolderBlock.Output.FileEntry] = [ GithubReadFolderBlock.Output.FileEntry( name=entry["name"], path=entry["path"], size=entry["size"], ) - for entry in content + for entry in data if entry["type"] == "file" ] - dirs = [ + + dirs: list[GithubReadFolderBlock.Output.DirEntry] = [ GithubReadFolderBlock.Output.DirEntry( name=entry["name"], path=entry["path"], ) - for entry in content + for entry in data if entry["type"] == "dir" ] return files, dirs - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - files, dirs = self.read_folder( + files, dirs = await self.read_folder( credentials, input_data.repo_url, input_data.folder_path.lstrip("/"), input_data.branch, ) - yield from (("file", file) for file in files) - yield from (("dir", dir) for dir in dirs) + for file in files: + yield "file", file + for dir in dirs: + yield "dir", dir class GithubMakeBranchBlock(Block): @@ -606,32 +615,35 @@ class GithubMakeBranchBlock(Block): ) @staticmethod - def create_branch( + async def create_branch( credentials: GithubCredentials, repo_url: str, new_branch: str, source_branch: str, ) -> str: api = get_api(credentials) - # Get the SHA of the source branch ref_url = repo_url + f"/git/refs/heads/{source_branch}" - response = api.get(ref_url) - sha = response.json()["object"]["sha"] + response = await api.get(ref_url) + data = response.json() + sha = data["object"]["sha"] # Create the new branch - create_ref_url = repo_url + "/git/refs" - data = {"ref": f"refs/heads/{new_branch}", "sha": sha} - response = api.post(create_ref_url, json=data) + new_ref_url = repo_url + "/git/refs" + data = { + "ref": f"refs/heads/{new_branch}", + "sha": sha, + } + response = await api.post(new_ref_url, json=data) return "Branch created successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.create_branch( + status = await self.create_branch( credentials, input_data.repo_url, input_data.new_branch, @@ -678,22 +690,22 @@ class GithubDeleteBranchBlock(Block): ) @staticmethod - def delete_branch( + async def delete_branch( credentials: GithubCredentials, repo_url: str, branch: str ) -> str: api = get_api(credentials) ref_url = repo_url + f"/git/refs/heads/{branch}" - api.delete(ref_url) + await api.delete(ref_url) return "Branch deleted successfully" - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - status = self.delete_branch( + status = await self.delete_branch( credentials, input_data.repo_url, input_data.branch, @@ -761,7 +773,7 @@ class GithubCreateFileBlock(Block): ) @staticmethod - def create_file( + async def create_file( credentials: GithubCredentials, repo_url: str, file_path: str, @@ -770,23 +782,18 @@ class GithubCreateFileBlock(Block): commit_message: str, ) -> tuple[str, str]: api = get_api(credentials) - # Convert content to base64 - content_bytes = content.encode("utf-8") - content_base64 = base64.b64encode(content_bytes).decode("utf-8") - - # Create the file using the GitHub API - contents_url = f"{repo_url}/contents/{file_path}" + contents_url = repo_url + f"/contents/{file_path}" + content_base64 = base64.b64encode(content.encode()).decode() data = { "message": commit_message, "content": content_base64, "branch": branch, } - response = api.put(contents_url, json=data) - result = response.json() + response = await api.put(contents_url, json=data) + data = response.json() + return data["content"]["html_url"], data["commit"]["sha"] - return result["content"]["html_url"], result["commit"]["sha"] - - def run( + async def run( self, input_data: Input, *, @@ -794,7 +801,7 @@ class GithubCreateFileBlock(Block): **kwargs, ) -> BlockOutput: try: - url, sha = self.create_file( + url, sha = await self.create_file( credentials, input_data.repo_url, input_data.file_path, @@ -866,7 +873,7 @@ class GithubUpdateFileBlock(Block): ) @staticmethod - def update_file( + async def update_file( credentials: GithubCredentials, repo_url: str, file_path: str, @@ -875,30 +882,24 @@ class GithubUpdateFileBlock(Block): commit_message: str, ) -> tuple[str, str]: api = get_api(credentials) - - # First get the current file to get its SHA - contents_url = f"{repo_url}/contents/{file_path}" + contents_url = repo_url + f"/contents/{file_path}" params = {"ref": branch} - response = api.get(contents_url, params=params) - current_file = response.json() + response = await api.get(contents_url, params=params) + data = response.json() # Convert new content to base64 - content_bytes = content.encode("utf-8") - content_base64 = base64.b64encode(content_bytes).decode("utf-8") - - # Update the file + content_base64 = base64.b64encode(content.encode()).decode() data = { "message": commit_message, "content": content_base64, - "sha": current_file["sha"], + "sha": data["sha"], "branch": branch, } - response = api.put(contents_url, json=data) - result = response.json() + response = await api.put(contents_url, json=data) + data = response.json() + return data["content"]["html_url"], data["commit"]["sha"] - return result["content"]["html_url"], result["commit"]["sha"] - - def run( + async def run( self, input_data: Input, *, @@ -906,7 +907,7 @@ class GithubUpdateFileBlock(Block): **kwargs, ) -> BlockOutput: try: - url, sha = self.update_file( + url, sha = await self.update_file( credentials, input_data.repo_url, input_data.file_path, @@ -981,7 +982,7 @@ class GithubCreateRepositoryBlock(Block): ) @staticmethod - def create_repository( + async def create_repository( credentials: GithubCredentials, name: str, description: str, @@ -989,24 +990,19 @@ class GithubCreateRepositoryBlock(Block): auto_init: bool, gitignore_template: str, ) -> tuple[str, str]: - api = get_api(credentials, convert_urls=False) # Disable URL conversion + api = get_api(credentials) data = { "name": name, "description": description, "private": private, "auto_init": auto_init, + "gitignore_template": gitignore_template, } + response = await api.post("https://api.github.com/user/repos", json=data) + data = response.json() + return data["html_url"], data["clone_url"] - if gitignore_template: - data["gitignore_template"] = gitignore_template - - # Create repository using the user endpoint - response = api.post("https://api.github.com/user/repos", json=data) - result = response.json() - - return result["html_url"], result["clone_url"] - - def run( + async def run( self, input_data: Input, *, @@ -1014,7 +1010,7 @@ class GithubCreateRepositoryBlock(Block): **kwargs, ) -> BlockOutput: try: - url, clone_url = self.create_repository( + url, clone_url = await self.create_repository( credentials, input_data.name, input_data.description, @@ -1081,17 +1077,13 @@ class GithubListStargazersBlock(Block): ) @staticmethod - def list_stargazers( + async def list_stargazers( credentials: GithubCredentials, repo_url: str ) -> list[Output.StargazerItem]: api = get_api(credentials) - # Add /stargazers to the repo URL to get stargazers endpoint - stargazers_url = f"{repo_url}/stargazers" - # Set accept header to get starred_at timestamp - headers = {"Accept": "application/vnd.github.star+json"} - response = api.get(stargazers_url, headers=headers) + stargazers_url = repo_url + "/stargazers" + response = await api.get(stargazers_url) data = response.json() - stargazers: list[GithubListStargazersBlock.Output.StargazerItem] = [ { "username": stargazer["login"], @@ -1101,18 +1093,16 @@ class GithubListStargazersBlock(Block): ] return stargazers - def run( + async def run( self, input_data: Input, *, credentials: GithubCredentials, **kwargs, ) -> BlockOutput: - try: - stargazers = self.list_stargazers( - credentials, - input_data.repo_url, - ) - yield from (("stargazer", stargazer) for stargazer in stargazers) - except Exception as e: - yield "error", str(e) + stargazers = await self.list_stargazers( + credentials, + input_data.repo_url, + ) + for stargazer in stargazers: + yield "stargazer", stargazer diff --git a/autogpt_platform/backend/backend/blocks/github/statuses.py b/autogpt_platform/backend/backend/blocks/github/statuses.py index a69b0e3d61..a7e2b006aa 100644 --- a/autogpt_platform/backend/backend/blocks/github/statuses.py +++ b/autogpt_platform/backend/backend/blocks/github/statuses.py @@ -115,7 +115,7 @@ class GithubCreateStatusBlock(Block): ) @staticmethod - def create_status( + async def create_status( credentials: GithubFineGrainedAPICredentials, repo_url: str, sha: str, @@ -144,7 +144,9 @@ class GithubCreateStatusBlock(Block): data.description = description status_url = f"{repo_url}/statuses/{sha}" - response = api.post(status_url, data=data.model_dump_json(exclude_none=True)) + response = await api.post( + status_url, data=data.model_dump_json(exclude_none=True) + ) result = response.json() return { @@ -158,7 +160,7 @@ class GithubCreateStatusBlock(Block): "updated_at": result["updated_at"], } - def run( + async def run( self, input_data: Input, *, @@ -166,7 +168,7 @@ class GithubCreateStatusBlock(Block): **kwargs, ) -> BlockOutput: try: - result = self.create_status( + result = await self.create_status( credentials=credentials, repo_url=input_data.repo_url, sha=input_data.sha, diff --git a/autogpt_platform/backend/backend/blocks/github/triggers.py b/autogpt_platform/backend/backend/blocks/github/triggers.py index 0410ed02a3..83b1689b89 100644 --- a/autogpt_platform/backend/backend/blocks/github/triggers.py +++ b/autogpt_platform/backend/backend/blocks/github/triggers.py @@ -53,7 +53,7 @@ class GitHubTriggerBase: description="Error message if the payload could not be processed" ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "payload", input_data.payload yield "triggered_by_user", input_data.payload["sender"] @@ -148,8 +148,9 @@ class GithubPullRequestTriggerBlock(GitHubTriggerBase, Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore - yield from super().run(input_data, **kwargs) + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value yield "event", input_data.payload["action"] yield "number", input_data.payload["number"] yield "pull_request", input_data.payload["pull_request"] diff --git a/autogpt_platform/backend/backend/blocks/google/calendar.py b/autogpt_platform/backend/backend/blocks/google/calendar.py index f119527f26..27cc9e5958 100644 --- a/autogpt_platform/backend/backend/blocks/google/calendar.py +++ b/autogpt_platform/backend/backend/blocks/google/calendar.py @@ -1,3 +1,4 @@ +import asyncio import enum import uuid from datetime import datetime, timedelta, timezone @@ -168,7 +169,7 @@ class GoogleCalendarReadEventsBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: try: @@ -180,7 +181,8 @@ class GoogleCalendarReadEventsBlock(Block): ) # Call Google Calendar API - result = self._read_calendar( + result = await asyncio.to_thread( + self._read_calendar, service=service, calendarId=input_data.calendar_id, time_min=input_data.start_time.isoformat(), @@ -477,12 +479,13 @@ class GoogleCalendarCreateEventBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: try: service = self._build_service(credentials, **kwargs) + # Create event body # Get start and end times based on the timing option if input_data.timing.discriminator == "exact_timing": start_datetime = input_data.timing.start_datetime @@ -543,7 +546,8 @@ class GoogleCalendarCreateEventBlock(Block): event_body["recurrence"] = [rule] # Create the event - result = self._create_event( + result = await asyncio.to_thread( + self._create_event, service=service, calendar_id=input_data.calendar_id, event_body=event_body, @@ -551,8 +555,9 @@ class GoogleCalendarCreateEventBlock(Block): conference_data_version=1 if input_data.add_google_meet else 0, ) - yield "event_id", result.get("id", "") - yield "event_link", result.get("htmlLink", "") + yield "event_id", result["id"] + yield "event_link", result["htmlLink"] + except Exception as e: yield "error", str(e) diff --git a/autogpt_platform/backend/backend/blocks/google/gmail.py b/autogpt_platform/backend/backend/blocks/google/gmail.py index 780cc1b16f..a6d2db3665 100644 --- a/autogpt_platform/backend/backend/blocks/google/gmail.py +++ b/autogpt_platform/backend/backend/blocks/google/gmail.py @@ -1,3 +1,4 @@ +import asyncio import base64 from email.utils import parseaddr from typing import List @@ -128,11 +129,13 @@ class GmailReadBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: - service = self._build_service(credentials, **kwargs) - messages = self._read_emails(service, input_data.query, input_data.max_results) + service = GmailReadBlock._build_service(credentials, **kwargs) + messages = await asyncio.to_thread( + self._read_emails, service, input_data.query, input_data.max_results + ) for email in messages: yield "email", email yield "emails", messages @@ -286,14 +289,18 @@ class GmailSendBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = GmailReadBlock._build_service(credentials, **kwargs) - send_result = self._send_email( - service, input_data.to, input_data.subject, input_data.body + result = await asyncio.to_thread( + self._send_email, + service, + input_data.to, + input_data.subject, + input_data.body, ) - yield "result", send_result + yield "result", result def _send_email(self, service, to: str, subject: str, body: str) -> dict: if not to or not subject or not body: @@ -358,12 +365,12 @@ class GmailListLabelsBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = GmailReadBlock._build_service(credentials, **kwargs) - labels = self._list_labels(service) - yield "result", labels + result = await asyncio.to_thread(self._list_labels, service) + yield "result", result def _list_labels(self, service) -> list[dict]: results = service.users().labels().list(userId="me").execute() @@ -419,11 +426,13 @@ class GmailAddLabelBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = GmailReadBlock._build_service(credentials, **kwargs) - result = self._add_label(service, input_data.message_id, input_data.label_name) + result = await asyncio.to_thread( + self._add_label, service, input_data.message_id, input_data.label_name + ) yield "result", result def _add_label(self, service, message_id: str, label_name: str) -> dict: @@ -502,12 +511,12 @@ class GmailRemoveLabelBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = GmailReadBlock._build_service(credentials, **kwargs) - result = self._remove_label( - service, input_data.message_id, input_data.label_name + result = await asyncio.to_thread( + self._remove_label, service, input_data.message_id, input_data.label_name ) yield "result", result diff --git a/autogpt_platform/backend/backend/blocks/google/sheets.py b/autogpt_platform/backend/backend/blocks/google/sheets.py index 141e359184..6a866051ff 100644 --- a/autogpt_platform/backend/backend/blocks/google/sheets.py +++ b/autogpt_platform/backend/backend/blocks/google/sheets.py @@ -1,3 +1,5 @@ +import asyncio + from google.oauth2.credentials import Credentials from googleapiclient.discovery import build @@ -68,11 +70,13 @@ class GoogleSheetsReadBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = self._build_service(credentials, **kwargs) - data = self._read_sheet(service, input_data.spreadsheet_id, input_data.range) + data = await asyncio.to_thread( + self._read_sheet, service, input_data.spreadsheet_id, input_data.range + ) yield "result", data @staticmethod @@ -157,11 +161,12 @@ class GoogleSheetsWriteBlock(Block): }, ) - def run( + async def run( self, input_data: Input, *, credentials: GoogleCredentials, **kwargs ) -> BlockOutput: service = GoogleSheetsReadBlock._build_service(credentials, **kwargs) - result = self._write_sheet( + result = await asyncio.to_thread( + self._write_sheet, service, input_data.spreadsheet_id, input_data.range, diff --git a/autogpt_platform/backend/backend/blocks/google_maps.py b/autogpt_platform/backend/backend/blocks/google_maps.py index 9e7f793531..01e81c69c9 100644 --- a/autogpt_platform/backend/backend/blocks/google_maps.py +++ b/autogpt_platform/backend/backend/blocks/google_maps.py @@ -103,7 +103,7 @@ class GoogleMapsSearchBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: places = self.search_places( diff --git a/autogpt_platform/backend/backend/blocks/helpers/http.py b/autogpt_platform/backend/backend/blocks/helpers/http.py index 33579ba0d9..f68b9f5a8b 100644 --- a/autogpt_platform/backend/backend/blocks/helpers/http.py +++ b/autogpt_platform/backend/backend/blocks/helpers/http.py @@ -1,14 +1,17 @@ from typing import Any, Optional -from backend.util.request import requests +from backend.util.request import Requests class GetRequest: @classmethod - def get_request( + async def get_request( cls, url: str, headers: Optional[dict] = None, json: bool = False ) -> Any: if headers is None: headers = {} - response = requests.get(url, headers=headers) - return response.json() if json else response.text + response = await Requests().get(url, headers=headers) + if json: + return response.json() + else: + return response.text() diff --git a/autogpt_platform/backend/backend/blocks/http.py b/autogpt_platform/backend/backend/blocks/http.py index d186e7f70b..9bb6d9b55e 100644 --- a/autogpt_platform/backend/backend/blocks/http.py +++ b/autogpt_platform/backend/backend/blocks/http.py @@ -1,10 +1,10 @@ import json import logging from enum import Enum -from io import BufferedReader +from io import BytesIO from pathlib import Path -from requests.exceptions import HTTPError, RequestException +import aiofiles from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField @@ -14,7 +14,7 @@ from backend.util.file import ( get_mime_type, store_media_file, ) -from backend.util.request import requests +from backend.util.request import Requests logger = logging.getLogger(name=__name__) @@ -77,54 +77,64 @@ class SendWebRequestBlock(Block): ) @staticmethod - def _prepare_files( + async def _prepare_files( graph_exec_id: str, files_name: str, files: list[MediaFileType], - ) -> tuple[list[tuple[str, tuple[str, BufferedReader, str]]], list[BufferedReader]]: - """Convert the `files` mapping into the structure expected by `requests`. - - Returns a tuple of (**files_payload**, **open_handles**) so we can close handles later. + ) -> list[tuple[str, tuple[str, BytesIO, str]]]: """ - files_payload: list[tuple[str, tuple[str, BufferedReader, str]]] = [] - open_handles: list[BufferedReader] = [] + Prepare files for the request by storing them and reading their content. + Returns a list of tuples in the format: + (files_name, (filename, BytesIO, mime_type)) + """ + files_payload: list[tuple[str, tuple[str, BytesIO, str]]] = [] for media in files: # Normalise to a list so we can repeat the same key - rel_path = store_media_file(graph_exec_id, media, return_content=False) + rel_path = await store_media_file( + graph_exec_id, media, return_content=False + ) abs_path = get_exec_file_path(graph_exec_id, rel_path) - try: - handle = open(abs_path, "rb") - except Exception as e: - for h in open_handles: - try: - h.close() - except Exception: - pass - raise RuntimeError(f"Failed to open file '{abs_path}': {e}") from e + async with aiofiles.open(abs_path, "rb") as f: + content = await f.read() + handle = BytesIO(content) + mime = get_mime_type(abs_path) + files_payload.append((files_name, (Path(abs_path).name, handle, mime))) - open_handles.append(handle) - mime = get_mime_type(abs_path) - files_payload.append((files_name, (Path(abs_path).name, handle, mime))) + return files_payload - return files_payload, open_handles - - def run(self, input_data: Input, *, graph_exec_id: str, **kwargs) -> BlockOutput: + async def run( + self, input_data: Input, *, graph_exec_id: str, **kwargs + ) -> BlockOutput: # ─── Parse/normalise body ──────────────────────────────────── body = input_data.body if isinstance(body, str): try: - body = json.loads(body) - except json.JSONDecodeError: - # plain text – treat as form‑field value instead + # Validate JSON string length to prevent DoS attacks + if len(body) > 10_000_000: # 10MB limit + raise ValueError("JSON body too large") + + parsed_body = json.loads(body) + + # Validate that parsed JSON is safe (basic object/array/primitive types) + if ( + isinstance(parsed_body, (dict, list, str, int, float, bool)) + or parsed_body is None + ): + body = parsed_body + else: + # Unexpected type, treat as plain text + input_data.json_format = False + + except (json.JSONDecodeError, ValueError): + # Invalid JSON or too large – treat as form‑field value instead input_data.json_format = False # ─── Prepare files (if any) ────────────────────────────────── use_files = bool(input_data.files) - files_payload: list[tuple[str, tuple[str, BufferedReader, str]]] = [] - open_handles: list[BufferedReader] = [] + files_payload: list[tuple[str, tuple[str, BytesIO, str]]] = [] if use_files: - files_payload, open_handles = self._prepare_files( + files_payload = await self._prepare_files( graph_exec_id, input_data.files_name, input_data.files ) @@ -135,47 +145,27 @@ class SendWebRequestBlock(Block): ) # ─── Execute request ───────────────────────────────────────── - try: - response = requests.request( - input_data.method.value, - input_data.url, - headers=input_data.headers, - files=files_payload if use_files else None, - # * If files → multipart ⇒ pass form‑fields via data= - data=body if not input_data.json_format else None, - # * Else, choose JSON vs url‑encoded based on flag - json=body if (input_data.json_format and not use_files) else None, - ) + response = await Requests().request( + input_data.method.value, + input_data.url, + headers=input_data.headers, + files=files_payload if use_files else None, + # * If files → multipart ⇒ pass form‑fields via data= + data=body if not input_data.json_format else None, + # * Else, choose JSON vs url‑encoded based on flag + json=body if (input_data.json_format and not use_files) else None, + ) - # Decide how to parse the response - if input_data.json_format or response.headers.get( - "content-type", "" - ).startswith("application/json"): - result = ( - None - if (response.status_code == 204 or not response.content.strip()) - else response.json() - ) - else: - result = response.text + # Decide how to parse the response + if response.headers.get("content-type", "").startswith("application/json"): + result = None if response.status == 204 else response.json() + else: + result = response.text() - # Yield according to status code bucket - if 200 <= response.status_code < 300: - yield "response", result - elif 400 <= response.status_code < 500: - yield "client_error", result - else: - yield "server_error", result - - except HTTPError as e: - yield "error", f"HTTP error: {str(e)}" - except RequestException as e: - yield "error", f"Request error: {str(e)}" - except Exception as e: - yield "error", str(e) - finally: - for h in open_handles: - try: - h.close() - except Exception: - pass + # Yield according to status code bucket + if 200 <= response.status < 300: + yield "response", result + elif 400 <= response.status < 500: + yield "client_error", result + else: + yield "server_error", result diff --git a/autogpt_platform/backend/backend/blocks/hubspot/company.py b/autogpt_platform/backend/backend/blocks/hubspot/company.py index 81d0fdaf9e..3026112259 100644 --- a/autogpt_platform/backend/backend/blocks/hubspot/company.py +++ b/autogpt_platform/backend/backend/blocks/hubspot/company.py @@ -5,7 +5,7 @@ from backend.blocks.hubspot._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class HubSpotCompanyBlock(Block): @@ -35,7 +35,7 @@ class HubSpotCompanyBlock(Block): output_schema=HubSpotCompanyBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs ) -> BlockOutput: base_url = "https://api.hubapi.com/crm/v3/objects/companies" @@ -45,7 +45,7 @@ class HubSpotCompanyBlock(Block): } if input_data.operation == "create": - response = requests.post( + response = await Requests().post( base_url, headers=headers, json={"properties": input_data.company_data} ) result = response.json() @@ -67,14 +67,16 @@ class HubSpotCompanyBlock(Block): } ] } - response = requests.post(search_url, headers=headers, json=search_data) - result = response.json() - yield "company", result.get("results", [{}])[0] + search_response = await Requests().post( + search_url, headers=headers, json=search_data + ) + search_result = search_response.json() + yield "search_company", search_result.get("results", [{}])[0] yield "status", "retrieved" elif input_data.operation == "update": # First get company ID by domain - search_response = requests.post( + search_response = await Requests().post( f"{base_url}/search", headers=headers, json={ @@ -91,10 +93,11 @@ class HubSpotCompanyBlock(Block): ] }, ) - company_id = search_response.json().get("results", [{}])[0].get("id") + search_result = search_response.json() + company_id = search_result.get("results", [{}])[0].get("id") if company_id: - response = requests.patch( + response = await Requests().patch( f"{base_url}/{company_id}", headers=headers, json={"properties": input_data.company_data}, diff --git a/autogpt_platform/backend/backend/blocks/hubspot/contact.py b/autogpt_platform/backend/backend/blocks/hubspot/contact.py index b27649e1dc..2029adaca1 100644 --- a/autogpt_platform/backend/backend/blocks/hubspot/contact.py +++ b/autogpt_platform/backend/backend/blocks/hubspot/contact.py @@ -5,7 +5,7 @@ from backend.blocks.hubspot._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class HubSpotContactBlock(Block): @@ -35,7 +35,7 @@ class HubSpotContactBlock(Block): output_schema=HubSpotContactBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs ) -> BlockOutput: base_url = "https://api.hubapi.com/crm/v3/objects/contacts" @@ -45,7 +45,7 @@ class HubSpotContactBlock(Block): } if input_data.operation == "create": - response = requests.post( + response = await Requests().post( base_url, headers=headers, json={"properties": input_data.contact_data} ) result = response.json() @@ -53,7 +53,6 @@ class HubSpotContactBlock(Block): yield "status", "created" elif input_data.operation == "get": - # Search for contact by email search_url = f"{base_url}/search" search_data = { "filterGroups": [ @@ -68,13 +67,15 @@ class HubSpotContactBlock(Block): } ] } - response = requests.post(search_url, headers=headers, json=search_data) + response = await Requests().post( + search_url, headers=headers, json=search_data + ) result = response.json() yield "contact", result.get("results", [{}])[0] yield "status", "retrieved" elif input_data.operation == "update": - search_response = requests.post( + search_response = await Requests().post( f"{base_url}/search", headers=headers, json={ @@ -91,10 +92,11 @@ class HubSpotContactBlock(Block): ] }, ) - contact_id = search_response.json().get("results", [{}])[0].get("id") + search_result = search_response.json() + contact_id = search_result.get("results", [{}])[0].get("id") if contact_id: - response = requests.patch( + response = await Requests().patch( f"{base_url}/{contact_id}", headers=headers, json={"properties": input_data.contact_data}, diff --git a/autogpt_platform/backend/backend/blocks/hubspot/engagement.py b/autogpt_platform/backend/backend/blocks/hubspot/engagement.py index 15d0296117..7e4dbc3d01 100644 --- a/autogpt_platform/backend/backend/blocks/hubspot/engagement.py +++ b/autogpt_platform/backend/backend/blocks/hubspot/engagement.py @@ -7,7 +7,7 @@ from backend.blocks.hubspot._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class HubSpotEngagementBlock(Block): @@ -42,7 +42,7 @@ class HubSpotEngagementBlock(Block): output_schema=HubSpotEngagementBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs ) -> BlockOutput: base_url = "https://api.hubapi.com" @@ -66,7 +66,9 @@ class HubSpotEngagementBlock(Block): } } - response = requests.post(email_url, headers=headers, json=email_data) + response = await Requests().post( + email_url, headers=headers, json=email_data + ) result = response.json() yield "result", result yield "status", "email_sent" @@ -80,7 +82,9 @@ class HubSpotEngagementBlock(Block): params = {"limit": 100, "after": from_date.isoformat()} - response = requests.get(engagement_url, headers=headers, params=params) + response = await Requests().get( + engagement_url, headers=headers, params=params + ) engagements = response.json() # Process engagement metrics diff --git a/autogpt_platform/backend/backend/blocks/ideogram.py b/autogpt_platform/backend/backend/blocks/ideogram.py index ca9ba69a80..468f8f1d1e 100644 --- a/autogpt_platform/backend/backend/blocks/ideogram.py +++ b/autogpt_platform/backend/backend/blocks/ideogram.py @@ -12,7 +12,7 @@ from backend.data.model import ( SchemaField, ) from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", @@ -196,13 +196,13 @@ class IdeogramModelBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: seed = input_data.seed # Step 1: Generate the image - result = self.run_model( + result = await self.run_model( api_key=credentials.api_key, model_name=input_data.ideogram_model_name.value, prompt=input_data.prompt, @@ -217,14 +217,14 @@ class IdeogramModelBlock(Block): # Step 2: Upscale the image if requested if input_data.upscale == UpscaleOption.AI_UPSCALE: - result = self.upscale_image( + result = await self.upscale_image( api_key=credentials.api_key, image_url=result, ) yield "result", result - def run_model( + async def run_model( self, api_key: SecretStr, model_name: str, @@ -267,12 +267,12 @@ class IdeogramModelBlock(Block): } try: - response = requests.post(url, json=data, headers=headers) + response = await Requests().post(url, headers=headers, json=data) return response.json()["data"][0]["url"] except RequestException as e: raise Exception(f"Failed to fetch image: {str(e)}") - def upscale_image(self, api_key: SecretStr, image_url: str): + async def upscale_image(self, api_key: SecretStr, image_url: str): url = "https://api.ideogram.ai/upscale" headers = { "Api-Key": api_key.get_secret_value(), @@ -280,21 +280,22 @@ class IdeogramModelBlock(Block): try: # Step 1: Download the image from the provided URL - image_response = requests.get(image_url) + response = await Requests().get(image_url) + image_content = response.content # Step 2: Send the downloaded image to the upscale API files = { - "image_file": ("image.png", image_response.content, "image/png"), + "image_file": ("image.png", image_content, "image/png"), } - response = requests.post( + response = await Requests().post( url, headers=headers, data={"image_request": "{}"}, files=files, ) - return response.json()["data"][0]["url"] + return (response.json())["data"][0]["url"] except RequestException as e: raise Exception(f"Failed to upscale image: {str(e)}") diff --git a/autogpt_platform/backend/backend/blocks/io.py b/autogpt_platform/backend/backend/blocks/io.py index f9f9b85fb0..c42e6c1dd7 100644 --- a/autogpt_platform/backend/backend/blocks/io.py +++ b/autogpt_platform/backend/backend/blocks/io.py @@ -95,7 +95,7 @@ class AgentInputBlock(Block): } ) - def run(self, input_data: Input, *args, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, *args, **kwargs) -> BlockOutput: if input_data.value is not None: yield "result", input_data.value @@ -186,7 +186,7 @@ class AgentOutputBlock(Block): static_output=True, ) - def run(self, input_data: Input, *args, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, *args, **kwargs) -> BlockOutput: """ Attempts to format the recorded_value using the fmt_string if provided. If formatting fails or no fmt_string is given, returns the original recorded_value. @@ -436,7 +436,7 @@ class AgentFileInputBlock(AgentInputBlock): ], ) - def run( + async def run( self, input_data: Input, *, @@ -446,7 +446,7 @@ class AgentFileInputBlock(AgentInputBlock): if not input_data.value: return - file_path = store_media_file( + file_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.value, return_content=False, diff --git a/autogpt_platform/backend/backend/blocks/iteration.py b/autogpt_platform/backend/backend/blocks/iteration.py index 0159e62d21..c0b66a2ed0 100644 --- a/autogpt_platform/backend/backend/blocks/iteration.py +++ b/autogpt_platform/backend/backend/blocks/iteration.py @@ -53,7 +53,7 @@ class StepThroughItemsBlock(Block): test_mock={}, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: for data in [input_data.items, input_data.items_object, input_data.items_str]: if not data: continue diff --git a/autogpt_platform/backend/backend/blocks/jina/chunking.py b/autogpt_platform/backend/backend/blocks/jina/chunking.py index 0ebc72f1ca..052fa8e815 100644 --- a/autogpt_platform/backend/backend/blocks/jina/chunking.py +++ b/autogpt_platform/backend/backend/blocks/jina/chunking.py @@ -5,7 +5,7 @@ from backend.blocks.jina._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class JinaChunkingBlock(Block): @@ -35,7 +35,7 @@ class JinaChunkingBlock(Block): output_schema=JinaChunkingBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: JinaCredentials, **kwargs ) -> BlockOutput: url = "https://segment.jina.ai/" @@ -55,7 +55,7 @@ class JinaChunkingBlock(Block): "max_chunk_length": str(input_data.max_chunk_length), } - response = requests.post(url, headers=headers, json=data) + response = await Requests().post(url, headers=headers, json=data) result = response.json() all_chunks.extend(result.get("chunks", [])) diff --git a/autogpt_platform/backend/backend/blocks/jina/embeddings.py b/autogpt_platform/backend/backend/blocks/jina/embeddings.py index 67a17bf2c3..abc2f9d6ae 100644 --- a/autogpt_platform/backend/backend/blocks/jina/embeddings.py +++ b/autogpt_platform/backend/backend/blocks/jina/embeddings.py @@ -5,7 +5,7 @@ from backend.blocks.jina._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests class JinaEmbeddingBlock(Block): @@ -29,7 +29,7 @@ class JinaEmbeddingBlock(Block): output_schema=JinaEmbeddingBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: JinaCredentials, **kwargs ) -> BlockOutput: url = "https://api.jina.ai/v1/embeddings" @@ -38,6 +38,6 @@ class JinaEmbeddingBlock(Block): "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", } data = {"input": input_data.texts, "model": input_data.model} - response = requests.post(url, headers=headers, json=data) + response = await Requests().post(url, headers=headers, json=data) embeddings = [e["embedding"] for e in response.json()["data"]] yield "embeddings", embeddings diff --git a/autogpt_platform/backend/backend/blocks/jina/fact_checker.py b/autogpt_platform/backend/backend/blocks/jina/fact_checker.py index c9b8c08d1d..9cf1e277fd 100644 --- a/autogpt_platform/backend/backend/blocks/jina/fact_checker.py +++ b/autogpt_platform/backend/backend/blocks/jina/fact_checker.py @@ -1,7 +1,5 @@ from urllib.parse import quote -import requests - from backend.blocks.jina._auth import ( JinaCredentials, JinaCredentialsField, @@ -9,6 +7,7 @@ from backend.blocks.jina._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField +from backend.util.request import Requests class FactCheckerBlock(Block): @@ -35,7 +34,7 @@ class FactCheckerBlock(Block): output_schema=FactCheckerBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: JinaCredentials, **kwargs ) -> BlockOutput: encoded_statement = quote(input_data.statement) @@ -46,8 +45,7 @@ class FactCheckerBlock(Block): "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", } - response = requests.get(url, headers=headers) - response.raise_for_status() + response = await Requests().get(url, headers=headers) data = response.json() if "data" in data: diff --git a/autogpt_platform/backend/backend/blocks/jina/search.py b/autogpt_platform/backend/backend/blocks/jina/search.py index 248d8af720..90a6eea51c 100644 --- a/autogpt_platform/backend/backend/blocks/jina/search.py +++ b/autogpt_platform/backend/backend/blocks/jina/search.py @@ -39,7 +39,7 @@ class SearchTheWebBlock(Block, GetRequest): test_mock={"get_request": lambda *args, **kwargs: "search content"}, ) - def run( + async def run( self, input_data: Input, *, credentials: JinaCredentials, **kwargs ) -> BlockOutput: # Encode the search query @@ -51,7 +51,7 @@ class SearchTheWebBlock(Block, GetRequest): # Prepend the Jina Search URL to the encoded query jina_search_url = f"https://s.jina.ai/{encoded_query}" - results = self.get_request(jina_search_url, headers=headers, json=False) + results = await self.get_request(jina_search_url, headers=headers, json=False) # Output the search results yield "results", results @@ -90,7 +90,7 @@ class ExtractWebsiteContentBlock(Block, GetRequest): test_mock={"get_request": lambda *args, **kwargs: "scraped content"}, ) - def run( + async def run( self, input_data: Input, *, credentials: JinaCredentials, **kwargs ) -> BlockOutput: if input_data.raw_content: @@ -103,5 +103,5 @@ class ExtractWebsiteContentBlock(Block, GetRequest): "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", } - content = self.get_request(url, json=False, headers=headers) + content = await self.get_request(url, json=False, headers=headers) yield "content", content diff --git a/autogpt_platform/backend/backend/blocks/linear/_api.py b/autogpt_platform/backend/backend/blocks/linear/_api.py index c43f46fa70..0acee7fada 100644 --- a/autogpt_platform/backend/backend/blocks/linear/_api.py +++ b/autogpt_platform/backend/backend/blocks/linear/_api.py @@ -48,7 +48,7 @@ class LinearClient: raise_for_status=False, ) - def _execute_graphql_request( + async def _execute_graphql_request( self, query: str, variables: dict | None = None ) -> Any: """ @@ -65,19 +65,18 @@ class LinearClient: if variables: payload["variables"] = variables - response = self._requests.post(self.API_URL, json=payload) + response = await self._requests.post(self.API_URL, json=payload) if not response.ok: - try: error_data = response.json() error_message = error_data.get("errors", [{}])[0].get("message", "") except json.JSONDecodeError: - error_message = response.text + error_message = response.text() raise LinearAPIException( - f"Linear API request failed ({response.status_code}): {error_message}", - response.status_code, + f"Linear API request failed ({response.status}): {error_message}", + response.status, ) response_data = response.json() @@ -88,12 +87,12 @@ class LinearClient: ] raise LinearAPIException( f"Linear API returned errors: {', '.join(error_messages)}", - response.status_code, + response.status, ) return response_data["data"] - def query(self, query: str, variables: Optional[dict] = None) -> dict: + async def query(self, query: str, variables: Optional[dict] = None) -> dict: """Executes a GraphQL query. Args: @@ -103,9 +102,9 @@ class LinearClient: Returns: The response data. """ - return self._execute_graphql_request(query, variables) + return await self._execute_graphql_request(query, variables) - def mutate(self, mutation: str, variables: Optional[dict] = None) -> dict: + async def mutate(self, mutation: str, variables: Optional[dict] = None) -> dict: """Executes a GraphQL mutation. Args: @@ -115,9 +114,11 @@ class LinearClient: Returns: The response data. """ - return self._execute_graphql_request(mutation, variables) + return await self._execute_graphql_request(mutation, variables) - def try_create_comment(self, issue_id: str, comment: str) -> CreateCommentResponse: + async def try_create_comment( + self, issue_id: str, comment: str + ) -> CreateCommentResponse: try: mutation = """ mutation CommentCreate($input: CommentCreateInput!) { @@ -138,13 +139,13 @@ class LinearClient: } } - added_comment = self.mutate(mutation, variables) + added_comment = await self.mutate(mutation, variables) # Select the commentCreate field from the mutation response return CreateCommentResponse(**added_comment["commentCreate"]) except LinearAPIException as e: raise e - def try_get_team_by_name(self, team_name: str) -> str: + async def try_get_team_by_name(self, team_name: str) -> str: try: query = """ query GetTeamId($searchTerm: String!) { @@ -167,12 +168,12 @@ class LinearClient: "searchTerm": team_name, } - team_id = self.query(query, variables) + team_id = await self.query(query, variables) return team_id["teams"]["nodes"][0]["id"] except LinearAPIException as e: raise e - def try_create_issue( + async def try_create_issue( self, team_id: str, title: str, @@ -211,12 +212,12 @@ class LinearClient: if priority: variables["input"]["priority"] = priority - added_issue = self.mutate(mutation, variables) + added_issue = await self.mutate(mutation, variables) return CreateIssueResponse(**added_issue["issueCreate"]) except LinearAPIException as e: raise e - def try_search_projects(self, term: str) -> list[Project]: + async def try_search_projects(self, term: str) -> list[Project]: try: query = """ query SearchProjects($term: String!, $includeComments: Boolean!) { @@ -238,14 +239,14 @@ class LinearClient: "includeComments": True, } - projects = self.query(query, variables) + projects = await self.query(query, variables) return [ Project(**project) for project in projects["searchProjects"]["nodes"] ] except LinearAPIException as e: raise e - def try_search_issues(self, term: str) -> list[Issue]: + async def try_search_issues(self, term: str) -> list[Issue]: try: query = """ query SearchIssues($term: String!, $includeComments: Boolean!) { @@ -266,7 +267,7 @@ class LinearClient: "includeComments": True, } - issues = self.query(query, variables) + issues = await self.query(query, variables) return [Issue(**issue) for issue in issues["searchIssues"]["nodes"]] except LinearAPIException as e: raise e diff --git a/autogpt_platform/backend/backend/blocks/linear/comment.py b/autogpt_platform/backend/backend/blocks/linear/comment.py index 6789fd12e3..d065609bf8 100644 --- a/autogpt_platform/backend/backend/blocks/linear/comment.py +++ b/autogpt_platform/backend/backend/blocks/linear/comment.py @@ -54,21 +54,21 @@ class LinearCreateCommentBlock(Block): ) @staticmethod - def create_comment( + async def create_comment( credentials: LinearCredentials, issue_id: str, comment: str ) -> tuple[str, str]: client = LinearClient(credentials=credentials) - response: CreateCommentResponse = client.try_create_comment( + response: CreateCommentResponse = await client.try_create_comment( issue_id=issue_id, comment=comment ) return response.comment.id, response.comment.body - def run( + async def run( self, input_data: Input, *, credentials: LinearCredentials, **kwargs ) -> BlockOutput: """Execute the comment creation""" try: - comment_id, comment_body = self.create_comment( + comment_id, comment_body = await self.create_comment( credentials=credentials, issue_id=input_data.issue_id, comment=input_data.comment, diff --git a/autogpt_platform/backend/backend/blocks/linear/issues.py b/autogpt_platform/backend/backend/blocks/linear/issues.py index f45e7fac0d..9f9d46d19a 100644 --- a/autogpt_platform/backend/backend/blocks/linear/issues.py +++ b/autogpt_platform/backend/backend/blocks/linear/issues.py @@ -67,7 +67,7 @@ class LinearCreateIssueBlock(Block): ) @staticmethod - def create_issue( + async def create_issue( credentials: LinearCredentials, team_name: str, title: str, @@ -76,15 +76,15 @@ class LinearCreateIssueBlock(Block): project_name: str | None = None, ) -> tuple[str, str]: client = LinearClient(credentials=credentials) - team_id = client.try_get_team_by_name(team_name=team_name) + team_id = await client.try_get_team_by_name(team_name=team_name) project_id: str | None = None if project_name: - projects = client.try_search_projects(term=project_name) + projects = await client.try_search_projects(term=project_name) if projects: project_id = projects[0].id else: raise LinearAPIException("Project not found", status_code=404) - response: CreateIssueResponse = client.try_create_issue( + response: CreateIssueResponse = await client.try_create_issue( team_id=team_id, title=title, description=description, @@ -93,12 +93,12 @@ class LinearCreateIssueBlock(Block): ) return response.issue.identifier, response.issue.title - def run( + async def run( self, input_data: Input, *, credentials: LinearCredentials, **kwargs ) -> BlockOutput: """Execute the issue creation""" try: - issue_id, issue_title = self.create_issue( + issue_id, issue_title = await self.create_issue( credentials=credentials, team_name=input_data.team_name, title=input_data.title, @@ -168,20 +168,22 @@ class LinearSearchIssuesBlock(Block): ) @staticmethod - def search_issues( + async def search_issues( credentials: LinearCredentials, term: str, ) -> list[Issue]: client = LinearClient(credentials=credentials) - response: list[Issue] = client.try_search_issues(term=term) + response: list[Issue] = await client.try_search_issues(term=term) return response - def run( + async def run( self, input_data: Input, *, credentials: LinearCredentials, **kwargs ) -> BlockOutput: """Execute the issue search""" try: - issues = self.search_issues(credentials=credentials, term=input_data.term) + issues = await self.search_issues( + credentials=credentials, term=input_data.term + ) yield "issues", issues except LinearAPIException as e: yield "error", str(e) diff --git a/autogpt_platform/backend/backend/blocks/linear/projects.py b/autogpt_platform/backend/backend/blocks/linear/projects.py index 695064a6a1..84f2b9ca53 100644 --- a/autogpt_platform/backend/backend/blocks/linear/projects.py +++ b/autogpt_platform/backend/backend/blocks/linear/projects.py @@ -69,20 +69,20 @@ class LinearSearchProjectsBlock(Block): ) @staticmethod - def search_projects( + async def search_projects( credentials: LinearCredentials, term: str, ) -> list[Project]: client = LinearClient(credentials=credentials) - response: list[Project] = client.try_search_projects(term=term) + response: list[Project] = await client.try_search_projects(term=term) return response - def run( + async def run( self, input_data: Input, *, credentials: LinearCredentials, **kwargs ) -> BlockOutput: """Execute the project search""" try: - projects = self.search_projects( + projects = await self.search_projects( credentials=credentials, term=input_data.term, ) diff --git a/autogpt_platform/backend/backend/blocks/llm.py b/autogpt_platform/backend/backend/blocks/llm.py index a1fedeec52..5230cb4428 100644 --- a/autogpt_platform/backend/backend/blocks/llm.py +++ b/autogpt_platform/backend/backend/blocks/llm.py @@ -3,14 +3,13 @@ import logging from abc import ABC from enum import Enum, EnumMeta from json import JSONDecodeError -from types import MappingProxyType from typing import Any, Iterable, List, Literal, NamedTuple, Optional import anthropic import ollama import openai from anthropic.types import ToolParam -from groq import Groq +from groq import AsyncGroq from pydantic import BaseModel, SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema @@ -24,7 +23,6 @@ from backend.data.model import ( from backend.integrations.providers import ProviderName from backend.util import json from backend.util.logging import TruncatedLogger -from backend.util.settings import BehaveAs, Settings from backend.util.text import TextFormatter logger = TruncatedLogger(logging.getLogger(__name__), "[LLM-Block]") @@ -73,20 +71,7 @@ class ModelMetadata(NamedTuple): class LlmModelMeta(EnumMeta): - @property - def __members__(self) -> MappingProxyType: - if Settings().config.behave_as == BehaveAs.LOCAL: - members = super().__members__ - return MappingProxyType(members) - else: - removed_providers = ["ollama"] - existing_members = super().__members__ - members = { - name: member - for name, member in existing_members.items() - if LlmModel[name].provider not in removed_providers - } - return MappingProxyType(members) + pass class LlmModel(str, Enum, metaclass=LlmModelMeta): @@ -328,7 +313,7 @@ def estimate_token_count(prompt_messages: list[dict]) -> int: return int(estimated_tokens * 1.2) -def llm_call( +async def llm_call( credentials: APIKeyCredentials, llm_model: LlmModel, prompt: list[dict], @@ -363,14 +348,14 @@ def llm_call( # Calculate available tokens based on context window and input length estimated_input_tokens = estimate_token_count(prompt) context_window = llm_model.context_window - model_max_output = llm_model.max_output_tokens or 4096 + model_max_output = llm_model.max_output_tokens or int(2**15) user_max = max_tokens or model_max_output available_tokens = max(context_window - estimated_input_tokens, 0) - max_tokens = max(min(available_tokens, model_max_output, user_max), 0) + max_tokens = max(min(available_tokens, model_max_output, user_max), 1) if provider == "openai": tools_param = tools if tools else openai.NOT_GIVEN - oai_client = openai.OpenAI(api_key=credentials.api_key.get_secret_value()) + oai_client = openai.AsyncOpenAI(api_key=credentials.api_key.get_secret_value()) response_format = None if llm_model in [LlmModel.O1_MINI, LlmModel.O1_PREVIEW]: @@ -383,7 +368,7 @@ def llm_call( elif json_format: response_format = {"type": "json_object"} - response = oai_client.chat.completions.create( + response = await oai_client.chat.completions.create( model=llm_model.value, messages=prompt, # type: ignore response_format=response_format, # type: ignore @@ -439,9 +424,11 @@ def llm_call( messages.append({"role": p["role"], "content": p["content"]}) last_role = p["role"] - client = anthropic.Anthropic(api_key=credentials.api_key.get_secret_value()) + client = anthropic.AsyncAnthropic( + api_key=credentials.api_key.get_secret_value() + ) try: - resp = client.messages.create( + resp = await client.messages.create( model=llm_model.value, system=sysprompt, messages=messages, @@ -495,9 +482,9 @@ def llm_call( if tools: raise ValueError("Groq does not support tools.") - client = Groq(api_key=credentials.api_key.get_secret_value()) + client = AsyncGroq(api_key=credentials.api_key.get_secret_value()) response_format = {"type": "json_object"} if json_format else None - response = client.chat.completions.create( + response = await client.chat.completions.create( model=llm_model.value, messages=prompt, # type: ignore response_format=response_format, # type: ignore @@ -515,10 +502,10 @@ def llm_call( if tools: raise ValueError("Ollama does not support tools.") - client = ollama.Client(host=ollama_host) + client = ollama.AsyncClient(host=ollama_host) sys_messages = [p["content"] for p in prompt if p["role"] == "system"] usr_messages = [p["content"] for p in prompt if p["role"] != "system"] - response = client.generate( + response = await client.generate( model=llm_model.value, prompt=f"{sys_messages}\n\n{usr_messages}", stream=False, @@ -534,12 +521,12 @@ def llm_call( ) elif provider == "open_router": tools_param = tools if tools else openai.NOT_GIVEN - client = openai.OpenAI( + client = openai.AsyncOpenAI( base_url="https://openrouter.ai/api/v1", api_key=credentials.api_key.get_secret_value(), ) - response = client.chat.completions.create( + response = await client.chat.completions.create( extra_headers={ "HTTP-Referer": "https://agpt.co", "X-Title": "AutoGPT", @@ -581,12 +568,12 @@ def llm_call( ) elif provider == "llama_api": tools_param = tools if tools else openai.NOT_GIVEN - client = openai.OpenAI( + client = openai.AsyncOpenAI( base_url="https://api.llama.com/compat/v1/", api_key=credentials.api_key.get_secret_value(), ) - response = client.chat.completions.create( + response = await client.chat.completions.create( extra_headers={ "HTTP-Referer": "https://agpt.co", "X-Title": "AutoGPT", @@ -676,6 +663,11 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): description="Expected format of the response. If provided, the response will be validated against this format. " "The keys should be the expected fields in the response, and the values should be the description of the field.", ) + list_result: bool = SchemaField( + title="List Result", + default=False, + description="Whether the response should be a list of objects in the expected format.", + ) model: LlmModel = SchemaField( title="LLM Model", default=LlmModel.GPT4O, @@ -715,7 +707,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): ) class Output(BlockSchema): - response: dict[str, Any] = SchemaField( + response: dict[str, Any] | list[dict[str, Any]] = SchemaField( description="The response object generated by the language model." ) prompt: list = SchemaField(description="The prompt sent to the language model.") @@ -759,7 +751,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): }, ) - def llm_call( + async def llm_call( self, credentials: APIKeyCredentials, llm_model: LlmModel, @@ -774,7 +766,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): so that it can be mocked withing the block testing framework. """ self.prompt = prompt - return llm_call( + return await llm_call( credentials=credentials, llm_model=llm_model, prompt=prompt, @@ -784,7 +776,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): ollama_host=ollama_host, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: logger.debug(f"Calling LLM with input data: {input_data}") @@ -806,13 +798,22 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): expected_format = [ f'"{k}": "{v}"' for k, v in input_data.expected_format.items() ] - format_prompt = ",\n ".join(expected_format) + if input_data.list_result: + format_prompt = ( + f'"results": [\n {{\n {", ".join(expected_format)}\n }}\n]' + ) + else: + format_prompt = "\n ".join(expected_format) + sys_prompt = trim_prompt( f""" |Reply strictly only in the following JSON format: |{{ | {format_prompt} |}} + | + |Ensure the response is valid JSON. Do not include any additional text outside of the JSON. + |If you cannot provide all the keys, provide an empty string for the values you cannot answer. """ ) prompt.append({"role": "system", "content": sys_prompt}) @@ -820,17 +821,16 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): if input_data.prompt: prompt.append({"role": "user", "content": input_data.prompt}) - def parse_response(resp: str) -> tuple[dict[str, Any], str | None]: + def validate_response(parsed: object) -> str | None: try: - parsed = json.loads(resp) if not isinstance(parsed, dict): - return {}, f"Expected a dictionary, but got {type(parsed)}" + return f"Expected a dictionary, but got {type(parsed)}" miss_keys = set(input_data.expected_format.keys()) - set(parsed.keys()) if miss_keys: - return parsed, f"Missing keys: {miss_keys}" - return parsed, None + return f"Missing keys: {miss_keys}" + return None except JSONDecodeError as e: - return {}, f"JSON decode error: {e}" + return f"JSON decode error: {e}" logger.info(f"LLM request: {prompt}") retry_prompt = "" @@ -838,7 +838,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): for retry_count in range(input_data.retry): try: - llm_response = self.llm_call( + llm_response = await self.llm_call( credentials=credentials, llm_model=llm_model, prompt=prompt, @@ -856,18 +856,29 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): logger.info(f"LLM attempt-{retry_count} response: {response_text}") if input_data.expected_format: - parsed_dict, parsed_error = parse_response(response_text) - if not parsed_error: - yield "response", { - k: ( - json.loads(v) - if isinstance(v, str) - and v.startswith("[") - and v.endswith("]") - else (", ".join(v) if isinstance(v, list) else v) + + response_obj = json.loads(response_text) + + if input_data.list_result and isinstance(response_obj, dict): + if "results" in response_obj: + response_obj = response_obj.get("results", []) + elif len(response_obj) == 1: + response_obj = list(response_obj.values()) + + response_error = "\n".join( + [ + validation_error + for response_item in ( + response_obj + if isinstance(response_obj, list) + else [response_obj] ) - for k, v in parsed_dict.items() - } + if (validation_error := validate_response(response_item)) + ] + ) + + if not response_error: + yield "response", response_obj yield "prompt", self.prompt return else: @@ -884,7 +895,7 @@ class AIStructuredResponseGeneratorBlock(AIBlockBase): | |And this is the error: |-- - |{parsed_error} + |{response_error} |-- """ ) @@ -978,17 +989,17 @@ class AITextGeneratorBlock(AIBlockBase): test_mock={"llm_call": lambda *args, **kwargs: "Response text"}, ) - def llm_call( + async def llm_call( self, input_data: AIStructuredResponseGeneratorBlock.Input, credentials: APIKeyCredentials, - ) -> str: + ) -> dict: block = AIStructuredResponseGeneratorBlock() - response = block.run_once(input_data, "response", credentials=credentials) + response = await block.run_once(input_data, "response", credentials=credentials) self.merge_llm_stats(block) return response["response"] - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: object_input_data = AIStructuredResponseGeneratorBlock.Input( @@ -998,7 +1009,8 @@ class AITextGeneratorBlock(AIBlockBase): }, expected_format={}, ) - yield "response", self.llm_call(object_input_data, credentials) + response = await self.llm_call(object_input_data, credentials) + yield "response", response yield "prompt", self.prompt @@ -1080,23 +1092,27 @@ class AITextSummarizerBlock(AIBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - for output_name, output_data in self._run(input_data, credentials): + async for output_name, output_data in self._run(input_data, credentials): yield output_name, output_data - def _run(self, input_data: Input, credentials: APIKeyCredentials) -> BlockOutput: + async def _run( + self, input_data: Input, credentials: APIKeyCredentials + ) -> BlockOutput: chunks = self._split_text( input_data.text, input_data.max_tokens, input_data.chunk_overlap ) summaries = [] for chunk in chunks: - chunk_summary = self._summarize_chunk(chunk, input_data, credentials) + chunk_summary = await self._summarize_chunk(chunk, input_data, credentials) summaries.append(chunk_summary) - final_summary = self._combine_summaries(summaries, input_data, credentials) + final_summary = await self._combine_summaries( + summaries, input_data, credentials + ) yield "summary", final_summary yield "prompt", self.prompt @@ -1112,22 +1128,22 @@ class AITextSummarizerBlock(AIBlockBase): return chunks - def llm_call( + async def llm_call( self, input_data: AIStructuredResponseGeneratorBlock.Input, credentials: APIKeyCredentials, ) -> dict: block = AIStructuredResponseGeneratorBlock() - response = block.run_once(input_data, "response", credentials=credentials) + response = await block.run_once(input_data, "response", credentials=credentials) self.merge_llm_stats(block) return response - def _summarize_chunk( + async def _summarize_chunk( self, chunk: str, input_data: Input, credentials: APIKeyCredentials ) -> str: prompt = f"Summarize the following text in a {input_data.style} form. Focus your summary on the topic of `{input_data.focus}` if present, otherwise just provide a general summary:\n\n```{chunk}```" - llm_response = self.llm_call( + llm_response = await self.llm_call( AIStructuredResponseGeneratorBlock.Input( prompt=prompt, credentials=input_data.credentials, @@ -1139,7 +1155,7 @@ class AITextSummarizerBlock(AIBlockBase): return llm_response["summary"] - def _combine_summaries( + async def _combine_summaries( self, summaries: list[str], input_data: Input, credentials: APIKeyCredentials ) -> str: combined_text = "\n\n".join(summaries) @@ -1147,7 +1163,7 @@ class AITextSummarizerBlock(AIBlockBase): if len(combined_text.split()) <= input_data.max_tokens: prompt = f"Provide a final summary of the following section summaries in a {input_data.style} form, focus your summary on the topic of `{input_data.focus}` if present:\n\n ```{combined_text}```\n\n Just respond with the final_summary in the format specified." - llm_response = self.llm_call( + llm_response = await self.llm_call( AIStructuredResponseGeneratorBlock.Input( prompt=prompt, credentials=input_data.credentials, @@ -1162,7 +1178,8 @@ class AITextSummarizerBlock(AIBlockBase): return llm_response["final_summary"] else: # If combined summaries are still too long, recursively summarize - return self._run( + block = AITextSummarizerBlock() + return await block.run_once( AITextSummarizerBlock.Input( text=combined_text, credentials=input_data.credentials, @@ -1170,10 +1187,9 @@ class AITextSummarizerBlock(AIBlockBase): max_tokens=input_data.max_tokens, chunk_overlap=input_data.chunk_overlap, ), + "summary", credentials=credentials, - ).send(None)[ - 1 - ] # Get the first yielded value + ) class AIConversationBlock(AIBlockBase): @@ -1244,20 +1260,20 @@ class AIConversationBlock(AIBlockBase): }, ) - def llm_call( + async def llm_call( self, input_data: AIStructuredResponseGeneratorBlock.Input, credentials: APIKeyCredentials, - ) -> str: + ) -> dict: block = AIStructuredResponseGeneratorBlock() - response = block.run_once(input_data, "response", credentials=credentials) + response = await block.run_once(input_data, "response", credentials=credentials) self.merge_llm_stats(block) - return response["response"] + return response - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - response = self.llm_call( + response = await self.llm_call( AIStructuredResponseGeneratorBlock.Input( prompt=input_data.prompt, credentials=input_data.credentials, @@ -1269,7 +1285,6 @@ class AIConversationBlock(AIBlockBase): ), credentials=credentials, ) - yield "response", response yield "prompt", self.prompt @@ -1363,13 +1378,15 @@ class AIListGeneratorBlock(AIBlockBase): }, ) - def llm_call( + async def llm_call( self, input_data: AIStructuredResponseGeneratorBlock.Input, credentials: APIKeyCredentials, ) -> dict[str, str]: llm_block = AIStructuredResponseGeneratorBlock() - response = llm_block.run_once(input_data, "response", credentials=credentials) + response = await llm_block.run_once( + input_data, "response", credentials=credentials + ) self.merge_llm_stats(llm_block) return response @@ -1392,7 +1409,7 @@ class AIListGeneratorBlock(AIBlockBase): logger.error(f"Failed to convert string to list: {e}") raise ValueError("Invalid list format. Could not convert to list.") - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: logger.debug(f"Starting AIListGeneratorBlock.run with input data: {input_data}") @@ -1458,7 +1475,7 @@ class AIListGeneratorBlock(AIBlockBase): for attempt in range(input_data.max_retries): try: logger.debug("Calling LLM") - llm_response = self.llm_call( + llm_response = await self.llm_call( AIStructuredResponseGeneratorBlock.Input( sys_prompt=sys_prompt, prompt=prompt, diff --git a/autogpt_platform/backend/backend/blocks/maths.py b/autogpt_platform/backend/backend/blocks/maths.py index cb65de1c09..0559d9673d 100644 --- a/autogpt_platform/backend/backend/blocks/maths.py +++ b/autogpt_platform/backend/backend/blocks/maths.py @@ -52,7 +52,7 @@ class CalculatorBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: operation = input_data.operation a = input_data.a b = input_data.b @@ -107,7 +107,7 @@ class CountItemsBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: collection = input_data.collection try: diff --git a/autogpt_platform/backend/backend/blocks/media.py b/autogpt_platform/backend/backend/blocks/media.py index 6c9afcd10a..15a3d5d17e 100644 --- a/autogpt_platform/backend/backend/blocks/media.py +++ b/autogpt_platform/backend/backend/blocks/media.py @@ -39,7 +39,7 @@ class MediaDurationBlock(Block): output_schema=MediaDurationBlock.Output, ) - def run( + async def run( self, input_data: Input, *, @@ -47,7 +47,7 @@ class MediaDurationBlock(Block): **kwargs, ) -> BlockOutput: # 1) Store the input media locally - local_media_path = store_media_file( + local_media_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.media_in, return_content=False, @@ -105,7 +105,7 @@ class LoopVideoBlock(Block): output_schema=LoopVideoBlock.Output, ) - def run( + async def run( self, input_data: Input, *, @@ -114,7 +114,7 @@ class LoopVideoBlock(Block): **kwargs, ) -> BlockOutput: # 1) Store the input video locally - local_video_path = store_media_file( + local_video_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.video_in, return_content=False, @@ -146,7 +146,7 @@ class LoopVideoBlock(Block): looped_clip.write_videofile(output_abspath, codec="libx264", audio_codec="aac") # Return as data URI - video_out = store_media_file( + video_out = await store_media_file( graph_exec_id=graph_exec_id, file=output_filename, return_content=input_data.output_return_type == "data_uri", @@ -194,7 +194,7 @@ class AddAudioToVideoBlock(Block): output_schema=AddAudioToVideoBlock.Output, ) - def run( + async def run( self, input_data: Input, *, @@ -203,12 +203,12 @@ class AddAudioToVideoBlock(Block): **kwargs, ) -> BlockOutput: # 1) Store the inputs locally - local_video_path = store_media_file( + local_video_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.video_in, return_content=False, ) - local_audio_path = store_media_file( + local_audio_path = await store_media_file( graph_exec_id=graph_exec_id, file=input_data.audio_in, return_content=False, @@ -236,7 +236,7 @@ class AddAudioToVideoBlock(Block): final_clip.write_videofile(output_abspath, codec="libx264", audio_codec="aac") # 5) Return either path or data URI - video_out = store_media_file( + video_out = await store_media_file( graph_exec_id=graph_exec_id, file=output_filename, return_content=input_data.output_return_type == "data_uri", diff --git a/autogpt_platform/backend/backend/blocks/medium.py b/autogpt_platform/backend/backend/blocks/medium.py index 6d871b4caa..a8964ca940 100644 --- a/autogpt_platform/backend/backend/blocks/medium.py +++ b/autogpt_platform/backend/backend/blocks/medium.py @@ -13,7 +13,7 @@ from backend.data.model import ( SecretField, ) from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", @@ -130,7 +130,7 @@ class PublishToMediumBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def create_post( + async def create_post( self, api_key: SecretStr, author_id, @@ -160,18 +160,17 @@ class PublishToMediumBlock(Block): "notifyFollowers": notify_followers, } - response = requests.post( + response = await Requests().post( f"https://api.medium.com/v1/users/{author_id}/posts", headers=headers, json=data, ) - return response.json() - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - response = self.create_post( + response = await self.create_post( credentials.api_key, input_data.author_id.get_secret_value(), input_data.title, diff --git a/autogpt_platform/backend/backend/blocks/mem0.py b/autogpt_platform/backend/backend/blocks/mem0.py index 64d7ca4bec..ad2c64f8f0 100644 --- a/autogpt_platform/backend/backend/blocks/mem0.py +++ b/autogpt_platform/backend/backend/blocks/mem0.py @@ -109,7 +109,7 @@ class AddMemoryBlock(Block, Mem0Base): test_mock={"_get_client": lambda credentials: MockMemoryClient()}, ) - def run( + async def run( self, input_data: Input, *, @@ -208,7 +208,7 @@ class SearchMemoryBlock(Block, Mem0Base): test_mock={"_get_client": lambda credentials: MockMemoryClient()}, ) - def run( + async def run( self, input_data: Input, *, @@ -288,7 +288,7 @@ class GetAllMemoriesBlock(Block, Mem0Base): test_mock={"_get_client": lambda credentials: MockMemoryClient()}, ) - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/nvidia/deepfake.py b/autogpt_platform/backend/backend/blocks/nvidia/deepfake.py index f7fa145312..f5205f6e72 100644 --- a/autogpt_platform/backend/backend/blocks/nvidia/deepfake.py +++ b/autogpt_platform/backend/backend/blocks/nvidia/deepfake.py @@ -5,7 +5,7 @@ from backend.blocks.nvidia._auth import ( ) from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import SchemaField -from backend.util.request import requests +from backend.util.request import Requests from backend.util.type import MediaFileType @@ -40,7 +40,7 @@ class NvidiaDeepfakeDetectBlock(Block): output_schema=NvidiaDeepfakeDetectBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: NvidiaCredentials, **kwargs ) -> BlockOutput: url = "https://ai.api.nvidia.com/v1/cv/hive/deepfake-image-detection" @@ -59,8 +59,7 @@ class NvidiaDeepfakeDetectBlock(Block): } try: - response = requests.post(url, headers=headers, json=payload) - response.raise_for_status() + response = await Requests().post(url, headers=headers, json=payload) data = response.json() result = data.get("data", [{}])[0] diff --git a/autogpt_platform/backend/backend/blocks/pinecone.py b/autogpt_platform/backend/backend/blocks/pinecone.py index 6f8a83a24c..529940b7cf 100644 --- a/autogpt_platform/backend/backend/blocks/pinecone.py +++ b/autogpt_platform/backend/backend/blocks/pinecone.py @@ -56,7 +56,7 @@ class PineconeInitBlock(Block): output_schema=PineconeInitBlock.Output, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: pc = Pinecone(api_key=credentials.api_key.get_secret_value()) @@ -117,7 +117,7 @@ class PineconeQueryBlock(Block): output_schema=PineconeQueryBlock.Output, ) - def run( + async def run( self, input_data: Input, *, @@ -195,7 +195,7 @@ class PineconeInsertBlock(Block): output_schema=PineconeInsertBlock.Output, ) - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/reddit.py b/autogpt_platform/backend/backend/blocks/reddit.py index b3dca4ca74..c88407f6d1 100644 --- a/autogpt_platform/backend/backend/blocks/reddit.py +++ b/autogpt_platform/backend/backend/blocks/reddit.py @@ -146,7 +146,7 @@ class GetRedditPostsBlock(Block): subreddit = client.subreddit(input_data.subreddit) return subreddit.new(limit=input_data.post_limit or 10) - def run( + async def run( self, input_data: Input, *, credentials: RedditCredentials, **kwargs ) -> BlockOutput: current_time = datetime.now(tz=timezone.utc) @@ -207,7 +207,7 @@ class PostRedditCommentBlock(Block): raise ValueError("Failed to post comment.") return new_comment.id - def run( + async def run( self, input_data: Input, *, credentials: RedditCredentials, **kwargs ) -> BlockOutput: yield "comment_id", self.reply_post(credentials, input_data.data) diff --git a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py index e75e0ad1cf..30744a28f7 100644 --- a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py +++ b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py @@ -159,7 +159,7 @@ class ReplicateFluxAdvancedModelBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: # If the seed is not provided, generate a random seed @@ -168,7 +168,7 @@ class ReplicateFluxAdvancedModelBlock(Block): seed = int.from_bytes(os.urandom(4), "big") # Run the model using the provided inputs - result = self.run_model( + result = await self.run_model( api_key=credentials.api_key, model_name=input_data.replicate_model_name.api_name, prompt=input_data.prompt, @@ -183,7 +183,7 @@ class ReplicateFluxAdvancedModelBlock(Block): ) yield "result", result - def run_model( + async def run_model( self, api_key: SecretStr, model_name, @@ -201,7 +201,7 @@ class ReplicateFluxAdvancedModelBlock(Block): client = ReplicateClient(api_token=api_key.get_secret_value()) # Run the model with additional parameters - output: FileOutput | list[FileOutput] = client.run( # type: ignore This is because they changed the return type, and didn't update the type hint! It should be overloaded depending on the value of `use_file_output` to `FileOutput | list[FileOutput]` but it's `Any | Iterator[Any]` + output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore This is because they changed the return type, and didn't update the type hint! It should be overloaded depending on the value of `use_file_output` to `FileOutput | list[FileOutput]` but it's `Any | Iterator[Any]` f"{model_name}", input={ "prompt": prompt, diff --git a/autogpt_platform/backend/backend/blocks/rss.py b/autogpt_platform/backend/backend/blocks/rss.py index 9a5a17ebee..e3f5da3139 100644 --- a/autogpt_platform/backend/backend/blocks/rss.py +++ b/autogpt_platform/backend/backend/blocks/rss.py @@ -1,4 +1,4 @@ -import time +import asyncio from datetime import datetime, timedelta, timezone from typing import Any @@ -87,7 +87,7 @@ class ReadRSSFeedBlock(Block): def parse_feed(url: str) -> dict[str, Any]: return feedparser.parse(url) # type: ignore - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: keep_going = True start_time = datetime.now(timezone.utc) - timedelta( minutes=input_data.time_period @@ -113,4 +113,4 @@ class ReadRSSFeedBlock(Block): ), ) - time.sleep(input_data.polling_rate) + await asyncio.sleep(input_data.polling_rate) diff --git a/autogpt_platform/backend/backend/blocks/sampling.py b/autogpt_platform/backend/backend/blocks/sampling.py index d2257db06f..ffd509ff75 100644 --- a/autogpt_platform/backend/backend/blocks/sampling.py +++ b/autogpt_platform/backend/backend/blocks/sampling.py @@ -93,7 +93,7 @@ class DataSamplingBlock(Block): ) self.accumulated_data = [] - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: if input_data.accumulate: if isinstance(input_data.data, dict): self.accumulated_data.append(input_data.data) diff --git a/autogpt_platform/backend/backend/blocks/screenshotone.py b/autogpt_platform/backend/backend/blocks/screenshotone.py index fed43b4281..7dd026a5c5 100644 --- a/autogpt_platform/backend/backend/blocks/screenshotone.py +++ b/autogpt_platform/backend/backend/blocks/screenshotone.py @@ -105,7 +105,7 @@ class ScreenshotWebPageBlock(Block): ) @staticmethod - def take_screenshot( + async def take_screenshot( credentials: APIKeyCredentials, graph_exec_id: str, url: str, @@ -121,11 +121,10 @@ class ScreenshotWebPageBlock(Block): """ Takes a screenshot using the ScreenshotOne API """ - api = Requests(trusted_origins=["https://api.screenshotone.com"]) + api = Requests() - # Build API URL with parameters + # Build API parameters params = { - "access_key": credentials.api_key.get_secret_value(), "url": url, "viewport_width": viewport_width, "viewport_height": viewport_height, @@ -137,19 +136,28 @@ class ScreenshotWebPageBlock(Block): "cache": str(cache).lower(), } - response = api.get("https://api.screenshotone.com/take", params=params) + # Make the API request + # Use header-based authentication instead of query parameter + headers = { + "X-Access-Key": credentials.api_key.get_secret_value(), + } + + response = await api.get( + "https://api.screenshotone.com/take", params=params, headers=headers + ) + content = response.content return { - "image": store_media_file( + "image": await store_media_file( graph_exec_id=graph_exec_id, file=MediaFileType( - f"data:image/{format.value};base64,{b64encode(response.content).decode('utf-8')}" + f"data:image/{format.value};base64,{b64encode(content).decode('utf-8')}" ), return_content=True, ) } - def run( + async def run( self, input_data: Input, *, @@ -158,7 +166,7 @@ class ScreenshotWebPageBlock(Block): **kwargs, ) -> BlockOutput: try: - screenshot_data = self.take_screenshot( + screenshot_data = await self.take_screenshot( credentials=credentials, graph_exec_id=graph_exec_id, url=input_data.url, diff --git a/autogpt_platform/backend/backend/blocks/search.py b/autogpt_platform/backend/backend/blocks/search.py index 633ad31091..51eadf215e 100644 --- a/autogpt_platform/backend/backend/blocks/search.py +++ b/autogpt_platform/backend/backend/blocks/search.py @@ -36,10 +36,10 @@ class GetWikipediaSummaryBlock(Block, GetRequest): test_mock={"get_request": lambda url, json: {"extract": "summary content"}}, ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: topic = input_data.topic url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}" - response = self.get_request(url, json=True) + response = await self.get_request(url, json=True) if "extract" not in response: raise RuntimeError(f"Unable to parse Wikipedia response: {response}") yield "summary", response["extract"] @@ -113,14 +113,14 @@ class GetWeatherInformationBlock(Block, GetRequest): test_credentials=TEST_CREDENTIALS, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: units = "metric" if input_data.use_celsius else "imperial" api_key = credentials.api_key location = input_data.location url = f"http://api.openweathermap.org/data/2.5/weather?q={quote(location)}&appid={api_key}&units={units}" - weather_data = self.get_request(url, json=True) + weather_data = await self.get_request(url, json=True) if "main" in weather_data and "weather" in weather_data: yield "temperature", str(weather_data["main"]["temp"]) diff --git a/autogpt_platform/backend/backend/blocks/slant3d/base.py b/autogpt_platform/backend/backend/blocks/slant3d/base.py index d5d1681e1d..e368a1b451 100644 --- a/autogpt_platform/backend/backend/blocks/slant3d/base.py +++ b/autogpt_platform/backend/backend/blocks/slant3d/base.py @@ -1,7 +1,7 @@ from typing import Any, Dict from backend.data.block import Block -from backend.util.request import requests +from backend.util.request import Requests from ._api import Color, CustomerDetails, OrderItem, Profile @@ -14,20 +14,25 @@ class Slant3DBlockBase(Block): def _get_headers(self, api_key: str) -> Dict[str, str]: return {"api-key": api_key, "Content-Type": "application/json"} - def _make_request(self, method: str, endpoint: str, api_key: str, **kwargs) -> Dict: + async def _make_request( + self, method: str, endpoint: str, api_key: str, **kwargs + ) -> Dict: url = f"{self.BASE_URL}/{endpoint}" - response = requests.request( + response = await Requests().request( method=method, url=url, headers=self._get_headers(api_key), **kwargs ) + resp = response.json() if not response.ok: - error_msg = response.json().get("error", "Unknown error") + error_msg = resp.get("error", "Unknown error") raise RuntimeError(f"API request failed: {error_msg}") - return response.json() + return resp - def _check_valid_color(self, profile: Profile, color: Color, api_key: str) -> str: - response = self._make_request( + async def _check_valid_color( + self, profile: Profile, color: Color, api_key: str + ) -> str: + response = await self._make_request( "GET", "filament", api_key, @@ -48,10 +53,12 @@ Valid colors for {profile.value} are: ) return color_tag - def _convert_to_color(self, profile: Profile, color: Color, api_key: str) -> str: - return self._check_valid_color(profile, color, api_key) + async def _convert_to_color( + self, profile: Profile, color: Color, api_key: str + ) -> str: + return await self._check_valid_color(profile, color, api_key) - def _format_order_data( + async def _format_order_data( self, customer: CustomerDetails, order_number: str, @@ -61,6 +68,7 @@ Valid colors for {profile.value} are: """Helper function to format order data for API requests""" orders = [] for item in items: + color_tag = await self._convert_to_color(item.profile, item.color, api_key) order_data = { "email": customer.email, "phone": customer.phone, @@ -85,9 +93,7 @@ Valid colors for {profile.value} are: "order_quantity": item.quantity, "order_image_url": "", "order_sku": "NOT_USED", - "order_item_color": self._convert_to_color( - item.profile, item.color, api_key - ), + "order_item_color": color_tag, "profile": item.profile.value, } orders.append(order_data) diff --git a/autogpt_platform/backend/backend/blocks/slant3d/filament.py b/autogpt_platform/backend/backend/blocks/slant3d/filament.py index c232c2ba8d..0659a45561 100644 --- a/autogpt_platform/backend/backend/blocks/slant3d/filament.py +++ b/autogpt_platform/backend/backend/blocks/slant3d/filament.py @@ -72,11 +72,11 @@ class Slant3DFilamentBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - result = self._make_request( + result = await self._make_request( "GET", "filament", credentials.api_key.get_secret_value() ) yield "filaments", result["filaments"] diff --git a/autogpt_platform/backend/backend/blocks/slant3d/order.py b/autogpt_platform/backend/backend/blocks/slant3d/order.py index a1a342a98e..f1cab18d27 100644 --- a/autogpt_platform/backend/backend/blocks/slant3d/order.py +++ b/autogpt_platform/backend/backend/blocks/slant3d/order.py @@ -1,8 +1,6 @@ import uuid from typing import List -import requests as baserequests - from backend.data.block import BlockOutput, BlockSchema from backend.data.model import APIKeyCredentials, SchemaField from backend.util import settings @@ -76,17 +74,17 @@ class Slant3DCreateOrderBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - order_data = self._format_order_data( + order_data = await self._format_order_data( input_data.customer, input_data.order_number, input_data.items, credentials.api_key.get_secret_value(), ) - result = self._make_request( + result = await self._make_request( "POST", "order", credentials.api_key.get_secret_value(), json=order_data ) yield "order_id", result["orderId"] @@ -162,28 +160,24 @@ class Slant3DEstimateOrderBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - order_data = self._format_order_data( + order_data = await self._format_order_data( input_data.customer, input_data.order_number, input_data.items, credentials.api_key.get_secret_value(), ) - try: - result = self._make_request( - "POST", - "order/estimate", - credentials.api_key.get_secret_value(), - json=order_data, - ) - yield "total_price", result["totalPrice"] - yield "shipping_cost", result["shippingCost"] - yield "printing_cost", result["printingCost"] - except baserequests.HTTPError as e: - yield "error", str(f"Error estimating order: {e} {e.response.text}") - raise + result = await self._make_request( + "POST", + "order/estimate", + credentials.api_key.get_secret_value(), + json=order_data, + ) + yield "total_price", result["totalPrice"] + yield "shipping_cost", result["shippingCost"] + yield "printing_cost", result["printingCost"] class Slant3DEstimateShippingBlock(Slant3DBlockBase): @@ -246,17 +240,17 @@ class Slant3DEstimateShippingBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - order_data = self._format_order_data( + order_data = await self._format_order_data( input_data.customer, input_data.order_number, input_data.items, credentials.api_key.get_secret_value(), ) - result = self._make_request( + result = await self._make_request( "POST", "order/estimateShipping", credentials.api_key.get_secret_value(), @@ -312,11 +306,11 @@ class Slant3DGetOrdersBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - result = self._make_request( + result = await self._make_request( "GET", "order", credentials.api_key.get_secret_value() ) yield "orders", [str(order["orderId"]) for order in result["ordersData"]] @@ -359,11 +353,11 @@ class Slant3DTrackingBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - result = self._make_request( + result = await self._make_request( "GET", f"order/{input_data.order_id}/get-tracking", credentials.api_key.get_secret_value(), @@ -403,11 +397,11 @@ class Slant3DCancelOrderBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - result = self._make_request( + result = await self._make_request( "DELETE", f"order/{input_data.order_id}", credentials.api_key.get_secret_value(), diff --git a/autogpt_platform/backend/backend/blocks/slant3d/slicing.py b/autogpt_platform/backend/backend/blocks/slant3d/slicing.py index 1b868efc9e..6abe3045ac 100644 --- a/autogpt_platform/backend/backend/blocks/slant3d/slicing.py +++ b/autogpt_platform/backend/backend/blocks/slant3d/slicing.py @@ -44,11 +44,11 @@ class Slant3DSlicerBlock(Slant3DBlockBase): }, ) - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: try: - result = self._make_request( + result = await self._make_request( "POST", "slicer", credentials.api_key.get_secret_value(), diff --git a/autogpt_platform/backend/backend/blocks/slant3d/webhook.py b/autogpt_platform/backend/backend/blocks/slant3d/webhook.py index 866a1c9afe..8a690cf1ad 100644 --- a/autogpt_platform/backend/backend/blocks/slant3d/webhook.py +++ b/autogpt_platform/backend/backend/blocks/slant3d/webhook.py @@ -37,7 +37,7 @@ class Slant3DTriggerBase: description="Error message if payload processing failed" ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "payload", input_data.payload yield "order_id", input_data.payload["orderId"] @@ -117,8 +117,9 @@ class Slant3DOrderWebhookBlock(Slant3DTriggerBase, Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore - yield from super().run(input_data, **kwargs) + async def run(self, input_data: Input, **kwargs) -> BlockOutput: # type: ignore + async for name, value in super().run(input_data, **kwargs): + yield name, value # Extract and normalize values from the payload yield "status", input_data.payload["status"] diff --git a/autogpt_platform/backend/backend/blocks/smart_decision_maker.py b/autogpt_platform/backend/backend/blocks/smart_decision_maker.py index dc45cf0fbb..2fe43af806 100644 --- a/autogpt_platform/backend/backend/blocks/smart_decision_maker.py +++ b/autogpt_platform/backend/backend/blocks/smart_decision_maker.py @@ -142,6 +142,12 @@ class SmartDecisionMakerBlock(Block): advanced=False, ) credentials: llm.AICredentials = llm.AICredentialsField() + multiple_tool_calls: bool = SchemaField( + title="Multiple Tool Calls", + default=False, + description="Whether to allow multiple tool calls in a single response.", + advanced=True, + ) sys_prompt: str = SchemaField( title="System Prompt", default="Thinking carefully step by step decide which function to call. " @@ -150,7 +156,7 @@ class SmartDecisionMakerBlock(Block): "matching the required jsonschema signature, no missing argument is allowed. " "If you have already completed the task objective, you can end the task " "by providing the end result of your work as a finish message. " - "Only provide EXACTLY one function call, multiple tool calls is strictly prohibited.", + "Function parameters that has no default value and not optional typed has to be provided. ", description="The system prompt to provide additional context to the model.", ) conversation_history: list[dict] = SchemaField( @@ -273,29 +279,18 @@ class SmartDecisionMakerBlock(Block): "name": SmartDecisionMakerBlock.cleanup(block.name), "description": block.description, } - + sink_block_input_schema = block.input_schema properties = {} - required = [] for link in links: - sink_block_input_schema = block.input_schema - description = ( - sink_block_input_schema.model_fields[link.sink_name].description - if link.sink_name in sink_block_input_schema.model_fields - and sink_block_input_schema.model_fields[link.sink_name].description - else f"The {link.sink_name} of the tool" + sink_name = SmartDecisionMakerBlock.cleanup(link.sink_name) + properties[sink_name] = sink_block_input_schema.get_field_schema( + link.sink_name ) - properties[SmartDecisionMakerBlock.cleanup(link.sink_name)] = { - "type": "string", - "description": description, - } tool_function["parameters"] = { - "type": "object", + **block.input_schema.jsonschema(), "properties": properties, - "required": required, - "additionalProperties": False, - "strict": True, } return {"type": "function", "function": tool_function} @@ -335,25 +330,27 @@ class SmartDecisionMakerBlock(Block): } properties = {} - required = [] for link in links: sink_block_input_schema = sink_node.input_default["input_schema"] + sink_block_properties = sink_block_input_schema.get("properties", {}).get( + link.sink_name, {} + ) + sink_name = SmartDecisionMakerBlock.cleanup(link.sink_name) description = ( - sink_block_input_schema["properties"][link.sink_name]["description"] - if "description" - in sink_block_input_schema["properties"][link.sink_name] + sink_block_properties["description"] + if "description" in sink_block_properties else f"The {link.sink_name} of the tool" ) - properties[SmartDecisionMakerBlock.cleanup(link.sink_name)] = { + properties[sink_name] = { "type": "string", "description": description, + "default": json.dumps(sink_block_properties.get("default", None)), } tool_function["parameters"] = { "type": "object", "properties": properties, - "required": required, "additionalProperties": False, "strict": True, } @@ -417,7 +414,7 @@ class SmartDecisionMakerBlock(Block): return return_tool_functions - def run( + async def run( self, input_data: Input, *, @@ -430,6 +427,7 @@ class SmartDecisionMakerBlock(Block): **kwargs, ) -> BlockOutput: tool_functions = self._create_function_signature(node_id) + yield "tool_functions", json.dumps(tool_functions) input_data.conversation_history = input_data.conversation_history or [] prompt = [json.to_dict(p) for p in input_data.conversation_history if p] @@ -469,6 +467,10 @@ class SmartDecisionMakerBlock(Block): ) prompt.extend(tool_output) + if input_data.multiple_tool_calls: + input_data.sys_prompt += "\nYou can call a tool (different tools) multiple times in a single response." + else: + input_data.sys_prompt += "\nOnly provide EXACTLY one function call, multiple tool calls is strictly prohibited." values = input_data.prompt_values if values: @@ -487,7 +489,7 @@ class SmartDecisionMakerBlock(Block): ): prompt.append({"role": "user", "content": prefix + input_data.prompt}) - response = llm.llm_call( + response = await llm.llm_call( credentials=credentials, llm_model=input_data.model, prompt=prompt, @@ -495,7 +497,7 @@ class SmartDecisionMakerBlock(Block): max_tokens=input_data.max_tokens, tools=tool_functions, ollama_host=input_data.ollama_host, - parallel_tool_calls=False, + parallel_tool_calls=True if input_data.multiple_tool_calls else None, ) if not response.tool_calls: @@ -506,8 +508,31 @@ class SmartDecisionMakerBlock(Block): tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) - for arg_name, arg_value in tool_args.items(): - yield f"tools_^_{tool_name}_~_{arg_name}", arg_value + # Find the tool definition to get the expected arguments + tool_def = next( + ( + tool + for tool in tool_functions + if tool["function"]["name"] == tool_name + ), + None, + ) + + if ( + tool_def + and "function" in tool_def + and "parameters" in tool_def["function"] + ): + expected_args = tool_def["function"]["parameters"].get("properties", {}) + else: + expected_args = tool_args.keys() + + # Yield provided arguments and None for missing ones + for arg_name in expected_args: + if arg_name in tool_args: + yield f"tools_^_{tool_name}_~_{arg_name}", tool_args[arg_name] + else: + yield f"tools_^_{tool_name}_~_{arg_name}", None response.prompt.append(response.raw_response) yield "conversations", response.prompt diff --git a/autogpt_platform/backend/backend/blocks/smartlead/_api.py b/autogpt_platform/backend/backend/blocks/smartlead/_api.py index 8caa266c2c..cff1c63b72 100644 --- a/autogpt_platform/backend/backend/blocks/smartlead/_api.py +++ b/autogpt_platform/backend/backend/blocks/smartlead/_api.py @@ -27,9 +27,11 @@ class SmartLeadClient: def _handle_error(self, e: Exception) -> str: return e.__str__().replace(self.api_key, "API KEY") - def create_campaign(self, request: CreateCampaignRequest) -> CreateCampaignResponse: + async def create_campaign( + self, request: CreateCampaignRequest + ) -> CreateCampaignResponse: try: - response = self.requests.post( + response = await self.requests.post( self._add_auth_to_url(f"{self.API_URL}/campaigns/create"), json=request.model_dump(), ) @@ -40,11 +42,11 @@ class SmartLeadClient: except Exception as e: raise ValueError(f"Failed to create campaign: {self._handle_error(e)}") - def add_leads_to_campaign( + async def add_leads_to_campaign( self, request: AddLeadsRequest ) -> AddLeadsToCampaignResponse: try: - response = self.requests.post( + response = await self.requests.post( self._add_auth_to_url( f"{self.API_URL}/campaigns/{request.campaign_id}/leads" ), @@ -64,7 +66,7 @@ class SmartLeadClient: f"Failed to add leads to campaign: {self._handle_error(e)}" ) - def save_campaign_sequences( + async def save_campaign_sequences( self, campaign_id: int, request: SaveSequencesRequest ) -> SaveSequencesResponse: """ @@ -84,13 +86,13 @@ class SmartLeadClient: - MANUAL_PERCENTAGE: Requires variant_distribution_percentage in seq_variants """ try: - response = self.requests.post( + response = await self.requests.post( self._add_auth_to_url( f"{self.API_URL}/campaigns/{campaign_id}/sequences" ), json=request.model_dump(exclude_none=True), ) - return SaveSequencesResponse(**response.json()) + return SaveSequencesResponse(**(response.json())) except Exception as e: raise ValueError( f"Failed to save campaign sequences: {e.__str__().replace(self.api_key, 'API KEY')}" diff --git a/autogpt_platform/backend/backend/blocks/smartlead/campaign.py b/autogpt_platform/backend/backend/blocks/smartlead/campaign.py index 3b4ef95749..0e6c72416b 100644 --- a/autogpt_platform/backend/backend/blocks/smartlead/campaign.py +++ b/autogpt_platform/backend/backend/blocks/smartlead/campaign.py @@ -80,20 +80,20 @@ class CreateCampaignBlock(Block): ) @staticmethod - def create_campaign( + async def create_campaign( name: str, credentials: SmartLeadCredentials ) -> CreateCampaignResponse: client = SmartLeadClient(credentials.api_key.get_secret_value()) - return client.create_campaign(CreateCampaignRequest(name=name)) + return await client.create_campaign(CreateCampaignRequest(name=name)) - def run( + async def run( self, input_data: Input, *, credentials: SmartLeadCredentials, **kwargs, ) -> BlockOutput: - response = self.create_campaign(input_data.name, credentials) + response = await self.create_campaign(input_data.name, credentials) yield "id", response.id yield "name", response.name @@ -193,11 +193,11 @@ class AddLeadToCampaignBlock(Block): ) @staticmethod - def add_leads_to_campaign( + async def add_leads_to_campaign( campaign_id: int, lead_list: list[LeadInput], credentials: SmartLeadCredentials ) -> AddLeadsToCampaignResponse: client = SmartLeadClient(credentials.api_key.get_secret_value()) - return client.add_leads_to_campaign( + return await client.add_leads_to_campaign( AddLeadsRequest( campaign_id=campaign_id, lead_list=lead_list, @@ -210,14 +210,14 @@ class AddLeadToCampaignBlock(Block): ), ) - def run( + async def run( self, input_data: Input, *, credentials: SmartLeadCredentials, **kwargs, ) -> BlockOutput: - response = self.add_leads_to_campaign( + response = await self.add_leads_to_campaign( input_data.campaign_id, input_data.lead_list, credentials ) @@ -297,22 +297,22 @@ class SaveCampaignSequencesBlock(Block): ) @staticmethod - def save_campaign_sequences( + async def save_campaign_sequences( campaign_id: int, sequences: list[Sequence], credentials: SmartLeadCredentials ) -> SaveSequencesResponse: client = SmartLeadClient(credentials.api_key.get_secret_value()) - return client.save_campaign_sequences( + return await client.save_campaign_sequences( campaign_id=campaign_id, request=SaveSequencesRequest(sequences=sequences) ) - def run( + async def run( self, input_data: Input, *, credentials: SmartLeadCredentials, **kwargs, ) -> BlockOutput: - response = self.save_campaign_sequences( + response = await self.save_campaign_sequences( input_data.campaign_id, input_data.sequences, credentials ) diff --git a/autogpt_platform/backend/backend/blocks/talking_head.py b/autogpt_platform/backend/backend/blocks/talking_head.py index b57a9b0da6..3861cb7752 100644 --- a/autogpt_platform/backend/backend/blocks/talking_head.py +++ b/autogpt_platform/backend/backend/blocks/talking_head.py @@ -1,4 +1,4 @@ -import time +import asyncio from typing import Literal from pydantic import SecretStr @@ -11,7 +11,7 @@ from backend.data.model import ( SchemaField, ) from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", @@ -113,26 +113,26 @@ class CreateTalkingAvatarVideoBlock(Block): test_credentials=TEST_CREDENTIALS, ) - def create_clip(self, api_key: SecretStr, payload: dict) -> dict: + async def create_clip(self, api_key: SecretStr, payload: dict) -> dict: url = "https://api.d-id.com/clips" headers = { "accept": "application/json", "content-type": "application/json", "authorization": f"Basic {api_key.get_secret_value()}", } - response = requests.post(url, json=payload, headers=headers) + response = await Requests().post(url, json=payload, headers=headers) return response.json() - def get_clip_status(self, api_key: SecretStr, clip_id: str) -> dict: + async def get_clip_status(self, api_key: SecretStr, clip_id: str) -> dict: url = f"https://api.d-id.com/clips/{clip_id}" headers = { "accept": "application/json", "authorization": f"Basic {api_key.get_secret_value()}", } - response = requests.get(url, headers=headers) + response = await Requests().get(url, headers=headers) return response.json() - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: # Create the clip @@ -153,12 +153,12 @@ class CreateTalkingAvatarVideoBlock(Block): "driver_id": input_data.driver_id, } - response = self.create_clip(credentials.api_key, payload) + response = await self.create_clip(credentials.api_key, payload) clip_id = response["id"] # Poll for clip status for _ in range(input_data.max_polling_attempts): - status_response = self.get_clip_status(credentials.api_key, clip_id) + status_response = await self.get_clip_status(credentials.api_key, clip_id) if status_response["status"] == "done": yield "video_url", status_response["result_url"] return @@ -167,6 +167,6 @@ class CreateTalkingAvatarVideoBlock(Block): f"Clip creation failed: {status_response.get('error', 'Unknown error')}" ) - time.sleep(input_data.polling_interval) + await asyncio.sleep(input_data.polling_interval) raise TimeoutError("Clip creation timed out") diff --git a/autogpt_platform/backend/backend/blocks/text.py b/autogpt_platform/backend/backend/blocks/text.py index 21351bbbd6..f4357a468c 100644 --- a/autogpt_platform/backend/backend/blocks/text.py +++ b/autogpt_platform/backend/backend/blocks/text.py @@ -43,7 +43,7 @@ class MatchTextPatternBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: output = input_data.data or input_data.text flags = 0 if not input_data.case_sensitive: @@ -133,7 +133,7 @@ class ExtractTextInformationBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: flags = 0 if not input_data.case_sensitive: flags = flags | re.IGNORECASE @@ -201,7 +201,7 @@ class FillTextTemplateBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "output", formatter.format_string(input_data.format, input_data.values) @@ -232,7 +232,7 @@ class CombineTextsBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: combined_text = input_data.delimiter.join(input_data.input) yield "output", combined_text @@ -267,7 +267,7 @@ class TextSplitBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: if len(input_data.text) == 0: yield "texts", [] else: @@ -301,5 +301,5 @@ class TextReplaceBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "output", input_data.text.replace(input_data.old, input_data.new) diff --git a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py index dbaef7c9bb..1599e2e26f 100644 --- a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py +++ b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py @@ -10,7 +10,7 @@ from backend.data.model import ( SchemaField, ) from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", @@ -71,7 +71,7 @@ class UnrealTextToSpeechBlock(Block): ) @staticmethod - def call_unreal_speech_api( + async def call_unreal_speech_api( api_key: SecretStr, text: str, voice_id: str ) -> dict[str, Any]: url = "https://api.v8.unrealspeech.com/speech" @@ -88,13 +88,13 @@ class UnrealTextToSpeechBlock(Block): "TimestampType": "sentence", } - response = requests.post(url, headers=headers, json=data) + response = await Requests().post(url, headers=headers, json=data) return response.json() - def run( + async def run( self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs ) -> BlockOutput: - api_response = self.call_unreal_speech_api( + api_response = await self.call_unreal_speech_api( credentials.api_key, input_data.text, input_data.voice_id, diff --git a/autogpt_platform/backend/backend/blocks/time_blocks.py b/autogpt_platform/backend/backend/blocks/time_blocks.py index adeeb3bee0..05d8e3699f 100644 --- a/autogpt_platform/backend/backend/blocks/time_blocks.py +++ b/autogpt_platform/backend/backend/blocks/time_blocks.py @@ -1,3 +1,4 @@ +import asyncio import time from datetime import datetime, timedelta from typing import Any, Union @@ -37,7 +38,7 @@ class GetCurrentTimeBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: current_time = time.strftime(input_data.format) yield "time", current_time @@ -87,7 +88,7 @@ class GetCurrentDateBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: offset = int(input_data.offset) except ValueError: @@ -132,7 +133,7 @@ class GetCurrentDateAndTimeBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: current_date_time = time.strftime(input_data.format) yield "date_time", current_date_time @@ -183,7 +184,7 @@ class CountdownTimerBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: seconds = int(input_data.seconds) minutes = int(input_data.minutes) hours = int(input_data.hours) @@ -192,5 +193,6 @@ class CountdownTimerBlock(Block): total_seconds = seconds + minutes * 60 + hours * 3600 + days * 86400 for _ in range(input_data.repeat): - time.sleep(total_seconds) + if total_seconds > 0: + await asyncio.sleep(total_seconds) yield "output_message", input_data.input_message diff --git a/autogpt_platform/backend/backend/blocks/todoist/comments.py b/autogpt_platform/backend/backend/blocks/todoist/comments.py index 210aa504b3..703afb696f 100644 --- a/autogpt_platform/backend/backend/blocks/todoist/comments.py +++ b/autogpt_platform/backend/backend/blocks/todoist/comments.py @@ -108,7 +108,7 @@ class TodoistCreateCommentBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -215,7 +215,7 @@ class TodoistGetCommentsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -307,7 +307,7 @@ class TodoistGetCommentBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -371,7 +371,7 @@ class TodoistUpdateCommentBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -429,7 +429,7 @@ class TodoistDeleteCommentBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/todoist/labels.py b/autogpt_platform/backend/backend/blocks/todoist/labels.py index fc1c381a42..4700ebb6c0 100644 --- a/autogpt_platform/backend/backend/blocks/todoist/labels.py +++ b/autogpt_platform/backend/backend/blocks/todoist/labels.py @@ -80,7 +80,7 @@ class TodoistCreateLabelBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -174,7 +174,7 @@ class TodoistListLabelsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -248,7 +248,7 @@ class TodoistGetLabelBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -321,7 +321,7 @@ class TodoistUpdateLabelBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -389,7 +389,7 @@ class TodoistDeleteLabelBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -444,7 +444,7 @@ class TodoistGetSharedLabelsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -499,7 +499,7 @@ class TodoistRenameSharedLabelsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -551,7 +551,7 @@ class TodoistRemoveSharedLabelsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/todoist/projects.py b/autogpt_platform/backend/backend/blocks/todoist/projects.py index 6955e0c136..33ad7950fa 100644 --- a/autogpt_platform/backend/backend/blocks/todoist/projects.py +++ b/autogpt_platform/backend/backend/blocks/todoist/projects.py @@ -95,7 +95,7 @@ class TodoistListProjectsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -185,7 +185,7 @@ class TodoistCreateProjectBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -277,7 +277,7 @@ class TodoistGetProjectBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -375,7 +375,7 @@ class TodoistUpdateProjectBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -438,7 +438,7 @@ class TodoistDeleteProjectBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -548,7 +548,7 @@ class TodoistListCollaboratorsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/todoist/sections.py b/autogpt_platform/backend/backend/blocks/todoist/sections.py index fd9273c0c3..764f7e166e 100644 --- a/autogpt_platform/backend/backend/blocks/todoist/sections.py +++ b/autogpt_platform/backend/backend/blocks/todoist/sections.py @@ -96,7 +96,7 @@ class TodoistListSectionsBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -166,7 +166,7 @@ class TodoistListSectionsBlock(Block): # except Exception as e: # raise e -# def run( +# async def run( # self, # input_data: Input, # *, @@ -238,7 +238,7 @@ class TodoistGetSectionBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -295,7 +295,7 @@ class TodoistDeleteSectionBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/todoist/tasks.py b/autogpt_platform/backend/backend/blocks/todoist/tasks.py index 67786efe11..d50124a9ef 100644 --- a/autogpt_platform/backend/backend/blocks/todoist/tasks.py +++ b/autogpt_platform/backend/backend/blocks/todoist/tasks.py @@ -130,7 +130,7 @@ class TodoistCreateTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -261,7 +261,7 @@ class TodoistGetTasksBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -345,7 +345,7 @@ class TodoistGetTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -452,7 +452,7 @@ class TodoistUpdateTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -539,7 +539,7 @@ class TodoistCloseTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -592,7 +592,7 @@ class TodoistReopenTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, @@ -645,7 +645,7 @@ class TodoistDeleteTaskBlock(Block): except Exception as e: raise e - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/direct_message/direct_message_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/direct_message/direct_message_lookup.py index 14aee69379..99c5bcab79 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/direct_message/direct_message_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/direct_message/direct_message_lookup.py @@ -162,7 +162,7 @@ # except tweepy.TweepyException: # raise -# def run( +# async def run( # self, # input_data: Input, # *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/direct_message/manage_direct_message.py b/autogpt_platform/backend/backend/blocks/twitter/direct_message/manage_direct_message.py index caeb470bf6..19fdb2819f 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/direct_message/manage_direct_message.py +++ b/autogpt_platform/backend/backend/blocks/twitter/direct_message/manage_direct_message.py @@ -122,7 +122,7 @@ # print(f"Unexpected error: {str(e)}") # raise -# def run( +# async def run( # self, # input_data: Input, # *, @@ -239,7 +239,7 @@ # print(f"Unexpected error: {str(e)}") # raise -# def run( +# async def run( # self, # input_data: Input, # *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/list_follows.py b/autogpt_platform/backend/backend/blocks/twitter/lists/list_follows.py index 10722ce146..62c6c05f0c 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/list_follows.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/list_follows.py @@ -68,7 +68,7 @@ class TwitterUnfollowListBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -131,7 +131,7 @@ class TwitterFollowListBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -276,7 +276,7 @@ class TwitterFollowListBlock(Block): # except tweepy.TweepyException: # raise -# def run( +# async def run( # self, # input_data: Input, # *, @@ -438,7 +438,7 @@ class TwitterFollowListBlock(Block): # except tweepy.TweepyException: # raise -# def run( +# async def run( # self, # input_data: Input, # *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/list_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/lists/list_lookup.py index 5860b6e165..6dbaf2b23d 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/list_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/list_lookup.py @@ -140,7 +140,7 @@ class TwitterGetListBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -312,7 +312,7 @@ class TwitterGetOwnedListsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/list_members.py b/autogpt_platform/backend/backend/blocks/twitter/lists/list_members.py index 8ad5125c20..9bcd8f15a2 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/list_members.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/list_members.py @@ -90,7 +90,7 @@ class TwitterRemoveListMemberBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -164,7 +164,7 @@ class TwitterAddListMemberBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -327,7 +327,7 @@ class TwitterGetListMembersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -493,7 +493,7 @@ class TwitterGetListMembershipsBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/list_tweets_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/lists/list_tweets_lookup.py index 5faa855019..bda25e1d2d 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/list_tweets_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/list_tweets_lookup.py @@ -178,7 +178,7 @@ class TwitterGetListTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/manage_lists.py b/autogpt_platform/backend/backend/blocks/twitter/lists/manage_lists.py index 800b1219c9..2ba8158f9c 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/manage_lists.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/manage_lists.py @@ -64,7 +64,7 @@ class TwitterDeleteListBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -158,7 +158,7 @@ class TwitterUpdateListBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -263,7 +263,7 @@ class TwitterCreateListBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/lists/pinned_lists.py b/autogpt_platform/backend/backend/blocks/twitter/lists/pinned_lists.py index 7da93b531a..a31d1059f6 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/lists/pinned_lists.py +++ b/autogpt_platform/backend/backend/blocks/twitter/lists/pinned_lists.py @@ -76,7 +76,7 @@ class TwitterUnpinListBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -140,7 +140,7 @@ class TwitterPinListBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -257,7 +257,7 @@ class TwitterGetPinnedListsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/spaces/search_spaces.py b/autogpt_platform/backend/backend/blocks/twitter/spaces/search_spaces.py index 2640d78bf9..77b28fa654 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/spaces/search_spaces.py +++ b/autogpt_platform/backend/backend/blocks/twitter/spaces/search_spaces.py @@ -158,7 +158,7 @@ class TwitterSearchSpacesBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/spaces/spaces_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/spaces/spaces_lookup.py index bf1f527970..d4ff5459e4 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/spaces/spaces_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/spaces/spaces_lookup.py @@ -186,7 +186,7 @@ class TwitterGetSpacesBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -341,7 +341,7 @@ class TwitterGetSpaceByIdBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -477,7 +477,7 @@ class TwitterGetSpaceBuyersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -618,7 +618,7 @@ class TwitterGetSpaceTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/bookmark.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/bookmark.py index 50add77be8..ec8976fc2f 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/bookmark.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/bookmark.py @@ -85,7 +85,7 @@ class TwitterBookmarkTweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -262,7 +262,7 @@ class TwitterGetBookmarkedTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -362,7 +362,7 @@ class TwitterRemoveBookmarkTweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/hide.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/hide.py index 78ab250c7f..65faa315ae 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/hide.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/hide.py @@ -68,7 +68,7 @@ class TwitterHideReplyBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -140,7 +140,7 @@ class TwitterUnhideReplyBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/like.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/like.py index 609c1c6bb4..8bbc30e8e9 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/like.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/like.py @@ -90,7 +90,7 @@ class TwitterLikeTweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -249,7 +249,7 @@ class TwitterGetLikingUsersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -467,7 +467,7 @@ class TwitterGetLikedTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -564,7 +564,7 @@ class TwitterUnlikeTweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/manage.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/manage.py index e9a59afec2..6dca0d74c8 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/manage.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/manage.py @@ -211,7 +211,7 @@ class TwitterPostTweetBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -288,7 +288,7 @@ class TwitterDeleteTweetBlock(Block): except Exception: raise - def run( + async def run( self, input_data: Input, *, @@ -508,7 +508,7 @@ class TwitterSearchRecentTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/quote.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/quote.py index 40df117b1c..b15271b072 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/quote.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/quote.py @@ -186,7 +186,7 @@ class TwitterGetQuoteTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/retweet.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/retweet.py index c0645e54fd..9b1ba81b78 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/retweet.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/retweet.py @@ -85,7 +85,7 @@ class TwitterRetweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -162,7 +162,7 @@ class TwitterRemoveRetweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -328,7 +328,7 @@ class TwitterGetRetweetersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/timeline.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/timeline.py index 1dcce6d12a..ca89039c2e 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/timeline.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/timeline.py @@ -234,7 +234,7 @@ class TwitterGetUserMentionsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -467,7 +467,7 @@ class TwitterGetHomeTimelineBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -713,7 +713,7 @@ class TwitterGetUserTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/tweets/tweet_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/tweets/tweet_lookup.py index a71eb11b60..5021161b9e 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/tweets/tweet_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/tweets/tweet_lookup.py @@ -153,7 +153,7 @@ class TwitterGetTweetBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -327,7 +327,7 @@ class TwitterGetTweetsBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/users/blocks.py b/autogpt_platform/backend/backend/blocks/twitter/users/blocks.py index c83632067a..ca118e91e2 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/users/blocks.py +++ b/autogpt_platform/backend/backend/blocks/twitter/users/blocks.py @@ -147,7 +147,7 @@ class TwitterGetBlockedUsersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/users/follows.py b/autogpt_platform/backend/backend/blocks/twitter/users/follows.py index a810750cbd..160ffe9b35 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/users/follows.py +++ b/autogpt_platform/backend/backend/blocks/twitter/users/follows.py @@ -82,7 +82,7 @@ class TwitterUnfollowUserBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -152,7 +152,7 @@ class TwitterFollowUserBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -308,7 +308,7 @@ class TwitterGetFollowersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -482,7 +482,7 @@ class TwitterGetFollowingBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/users/mutes.py b/autogpt_platform/backend/backend/blocks/twitter/users/mutes.py index 6be5534bee..36bb4028f9 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/users/mutes.py +++ b/autogpt_platform/backend/backend/blocks/twitter/users/mutes.py @@ -82,7 +82,7 @@ class TwitterUnmuteUserBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -232,7 +232,7 @@ class TwitterGetMutedUsersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -318,7 +318,7 @@ class TwitterMuteUserBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/twitter/users/user_lookup.py b/autogpt_platform/backend/backend/blocks/twitter/users/user_lookup.py index 7c4bc8322d..585ebff3db 100644 --- a/autogpt_platform/backend/backend/blocks/twitter/users/user_lookup.py +++ b/autogpt_platform/backend/backend/blocks/twitter/users/user_lookup.py @@ -168,7 +168,7 @@ class TwitterGetUserBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, @@ -357,7 +357,7 @@ class TwitterGetUsersBlock(Block): except tweepy.TweepyException: raise - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/blocks/xml_parser.py b/autogpt_platform/backend/backend/blocks/xml_parser.py index 523fe1a69d..a3d5854499 100644 --- a/autogpt_platform/backend/backend/blocks/xml_parser.py +++ b/autogpt_platform/backend/backend/blocks/xml_parser.py @@ -25,7 +25,7 @@ class XMLParserBlock(Block): ], ) - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: try: tokens = tokenize(input_data.input_xml) parser = Parser(tokens) diff --git a/autogpt_platform/backend/backend/blocks/youtube.py b/autogpt_platform/backend/backend/blocks/youtube.py index 648d9d6dae..cc16c4ae41 100644 --- a/autogpt_platform/backend/backend/blocks/youtube.py +++ b/autogpt_platform/backend/backend/blocks/youtube.py @@ -79,7 +79,7 @@ class TranscribeYoutubeVideoBlock(Block): except Exception: raise ValueError(f"No transcripts found for the video: {video_id}") - def run(self, input_data: Input, **kwargs) -> BlockOutput: + async def run(self, input_data: Input, **kwargs) -> BlockOutput: video_id = self.extract_video_id(input_data.youtube_url) yield "video_id", video_id diff --git a/autogpt_platform/backend/backend/blocks/zerobounce/validate_emails.py b/autogpt_platform/backend/backend/blocks/zerobounce/validate_emails.py index ee87a8f285..b23e822ddc 100644 --- a/autogpt_platform/backend/backend/blocks/zerobounce/validate_emails.py +++ b/autogpt_platform/backend/backend/blocks/zerobounce/validate_emails.py @@ -159,7 +159,7 @@ class ValidateEmailsBlock(Block): client = ZeroBounceClient(credentials.api_key.get_secret_value()) return client.validate_email(email, ip_address) - def run( + async def run( self, input_data: Input, *, diff --git a/autogpt_platform/backend/backend/cli.py b/autogpt_platform/backend/backend/cli.py index 87fadb88de..988961b2de 100755 --- a/autogpt_platform/backend/backend/cli.py +++ b/autogpt_platform/backend/backend/cli.py @@ -117,20 +117,21 @@ def test(): @test.command() @click.argument("server_address") -def reddit(server_address: str): +async def reddit(server_address: str): """ Create an event graph """ - import requests - from backend.usecases.reddit_marketing import create_test_graph + from backend.util.request import Requests test_graph = create_test_graph() url = f"{server_address}/graphs" headers = {"Content-Type": "application/json"} data = test_graph.model_dump_json() - response = requests.post(url, headers=headers, data=data) + response = await Requests(trusted_origins=[server_address]).post( + url, headers=headers, data=data + ) graph_id = response.json()["id"] print(f"Graph created with ID: {graph_id}") @@ -138,28 +139,32 @@ def reddit(server_address: str): @test.command() @click.argument("server_address") -def populate_db(server_address: str): +async def populate_db(server_address: str): """ Create an event graph """ - import requests from backend.usecases.sample import create_test_graph + from backend.util.request import Requests test_graph = create_test_graph() url = f"{server_address}/graphs" headers = {"Content-Type": "application/json"} data = test_graph.model_dump_json() - response = requests.post(url, headers=headers, data=data) + response = await Requests(trusted_origins=[server_address]).post( + url, headers=headers, data=data + ) graph_id = response.json()["id"] - if response.status_code == 200: + if response.status == 200: execute_url = f"{server_address}/graphs/{response.json()['id']}/execute" text = "Hello, World!" input_data = {"input": text} - response = requests.post(execute_url, headers=headers, json=input_data) + response = Requests(trusted_origins=[server_address]).post( + execute_url, headers=headers, json=input_data + ) schedule_url = f"{server_address}/graphs/{graph_id}/schedules" data = { @@ -167,51 +172,60 @@ def populate_db(server_address: str): "cron": "*/5 * * * *", "input_data": {"input": "Hello, World!"}, } - response = requests.post(schedule_url, headers=headers, json=data) + response = Requests(trusted_origins=[server_address]).post( + schedule_url, headers=headers, json=data + ) print("Database populated with: \n- graph\n- execution\n- schedule") @test.command() @click.argument("server_address") -def graph(server_address: str): +async def graph(server_address: str): """ Create an event graph """ - import requests from backend.usecases.sample import create_test_graph + from backend.util.request import Requests url = f"{server_address}/graphs" headers = {"Content-Type": "application/json"} data = create_test_graph().model_dump_json() - response = requests.post(url, headers=headers, data=data) + response = await Requests(trusted_origins=[server_address]).post( + url, headers=headers, data=data + ) - if response.status_code == 200: + if response.status == 200: print(response.json()["id"]) execute_url = f"{server_address}/graphs/{response.json()['id']}/execute" text = "Hello, World!" input_data = {"input": text} - response = requests.post(execute_url, headers=headers, json=input_data) + response = await Requests(trusted_origins=[server_address]).post( + execute_url, headers=headers, json=input_data + ) else: print("Failed to send graph") - print(f"Response: {response.text}") + print(f"Response: {response.text()}") @test.command() @click.argument("graph_id") @click.argument("content") -def execute(graph_id: str, content: dict): +async def execute(graph_id: str, content: dict): """ Create an event graph """ - import requests + + from backend.util.request import Requests headers = {"Content-Type": "application/json"} execute_url = f"http://0.0.0.0:8000/graphs/{graph_id}/execute" - requests.post(execute_url, headers=headers, json=content) + await Requests(trusted_origins=["http://0.0.0.0:8000"]).post( + execute_url, headers=headers, json=content + ) @test.command() diff --git a/autogpt_platform/backend/backend/data/block.py b/autogpt_platform/backend/backend/data/block.py index eca44fd7cd..5283997d4c 100644 --- a/autogpt_platform/backend/backend/data/block.py +++ b/autogpt_platform/backend/backend/data/block.py @@ -1,12 +1,12 @@ import functools import inspect from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator as AsyncGen from enum import Enum from typing import ( TYPE_CHECKING, Any, ClassVar, - Generator, Generic, Optional, Sequence, @@ -42,7 +42,7 @@ app_config = Config() BlockData = tuple[str, Any] # Input & Output data should be a tuple of (name, data). BlockInput = dict[str, Any] # Input: 1 input pin consumes 1 data. -BlockOutput = Generator[BlockData, None, None] # Output: 1 output pin produces n data. +BlockOutput = AsyncGen[BlockData, None] # Output: 1 output pin produces n data. CompletedBlockOutput = dict[str, list[Any]] # Completed stream, collected as a dict. @@ -118,7 +118,10 @@ class BlockSchema(BaseModel): @classmethod def validate_data(cls, data: BlockInput) -> str | None: - return json.validate_with_jsonschema(schema=cls.jsonschema(), data=data) + return json.validate_with_jsonschema( + schema=cls.jsonschema(), + data={k: v for k, v in data.items() if v is not None}, + ) @classmethod def get_mismatch_error(cls, data: BlockInput) -> str | None: @@ -388,7 +391,7 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): return cls() @abstractmethod - def run(self, input_data: BlockSchemaInputType, **kwargs) -> BlockOutput: + async def run(self, input_data: BlockSchemaInputType, **kwargs) -> BlockOutput: """ Run the block with the given input data. Args: @@ -406,10 +409,16 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): output_name: One of the output name defined in Block's output_schema. output_data: The data for the output_name, matching the defined schema. """ - pass + # --- satisfy the type checker, never executed ------------- + if False: # noqa: SIM115 + yield "name", "value" # pyright: ignore[reportMissingYield] + raise NotImplementedError(f"{self.name} does not implement the run method.") - def run_once(self, input_data: BlockSchemaInputType, output: str, **kwargs) -> Any: - for name, data in self.run(input_data, **kwargs): + async def run_once( + self, input_data: BlockSchemaInputType, output: str, **kwargs + ) -> Any: + async for item in self.run(input_data, **kwargs): + name, data = item if name == output: return data raise ValueError(f"{self.name} did not produce any output for {output}") @@ -458,14 +467,15 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): "uiType": self.block_type.value, } - def execute(self, input_data: BlockInput, **kwargs) -> BlockOutput: + async def execute(self, input_data: BlockInput, **kwargs) -> BlockOutput: if error := self.input_schema.validate_data(input_data): raise ValueError( f"Unable to execute block with invalid input data: {error}" ) - for output_name, output_data in self.run( - self.input_schema(**input_data), **kwargs + async for output_name, output_data in self.run( + self.input_schema(**{k: v for k, v in input_data.items() if v is not None}), + **kwargs, ): if output_name == "error": raise RuntimeError(output_data) diff --git a/autogpt_platform/backend/backend/data/block_cost_config.py b/autogpt_platform/backend/backend/data/block_cost_config.py index 864ed0e989..730512e4ea 100644 --- a/autogpt_platform/backend/backend/data/block_cost_config.py +++ b/autogpt_platform/backend/backend/data/block_cost_config.py @@ -2,6 +2,8 @@ from typing import Type from backend.blocks.ai_music_generator import AIMusicGeneratorBlock from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock +from backend.blocks.apollo.organization import SearchOrganizationsBlock +from backend.blocks.apollo.people import SearchPeopleBlock from backend.blocks.flux_kontext import AIImageEditorBlock, FluxKontextModelName from backend.blocks.ideogram import IdeogramModelBlock from backend.blocks.jina.embeddings import JinaEmbeddingBlock @@ -24,6 +26,7 @@ from backend.data.cost import BlockCost, BlockCostType from backend.integrations.credentials_store import ( aiml_api_credentials, anthropic_credentials, + apollo_credentials, did_credentials, groq_credentials, ideogram_credentials, @@ -345,4 +348,28 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = { ) ], SmartDecisionMakerBlock: LLM_COST, + SearchOrganizationsBlock: [ + BlockCost( + cost_amount=2, + cost_filter={ + "credentials": { + "id": apollo_credentials.id, + "provider": apollo_credentials.provider, + "type": apollo_credentials.type, + } + }, + ) + ], + SearchPeopleBlock: [ + BlockCost( + cost_amount=2, + cost_filter={ + "credentials": { + "id": apollo_credentials.id, + "provider": apollo_credentials.provider, + "type": apollo_credentials.type, + } + }, + ) + ], } diff --git a/autogpt_platform/backend/backend/data/execution.py b/autogpt_platform/backend/backend/data/execution.py index 757c788f2f..e97bb30452 100644 --- a/autogpt_platform/backend/backend/data/execution.py +++ b/autogpt_platform/backend/backend/data/execution.py @@ -50,6 +50,7 @@ from .block import ( from .db import BaseDbModel from .includes import ( EXECUTION_RESULT_INCLUDE, + EXECUTION_RESULT_ORDER, GRAPH_EXECUTION_INCLUDE_WITH_NODES, graph_execution_include, ) @@ -555,18 +556,18 @@ async def upsert_execution_input( async def upsert_execution_output( node_exec_id: str, output_name: str, - output_data: Any, + output_data: Any | None, ) -> None: """ Insert AgentNodeExecutionInputOutput record for as one of AgentNodeExecution.Output. """ - await AgentNodeExecutionInputOutput.prisma().create( - data=AgentNodeExecutionInputOutputCreateInput( - name=output_name, - data=Json(output_data), - referencedByOutputExecId=node_exec_id, - ) + data = AgentNodeExecutionInputOutputCreateInput( + name=output_name, + referencedByOutputExecId=node_exec_id, ) + if output_data is not None: + data["data"] = Json(output_data) + await AgentNodeExecutionInputOutput.prisma().create(data=data) async def update_graph_execution_start_time( @@ -744,6 +745,7 @@ async def get_node_executions( executions = await AgentNodeExecution.prisma().find_many( where=where_clause, include=EXECUTION_RESULT_INCLUDE, + order=EXECUTION_RESULT_ORDER, take=limit, ) res = [NodeExecutionResult.from_db(execution) for execution in executions] @@ -765,11 +767,8 @@ async def get_latest_node_execution( {"executionStatus": ExecutionStatus.FAILED}, ], }, - order=[ - {"queuedTime": "desc"}, - {"addedTime": "desc"}, - ], include=EXECUTION_RESULT_INCLUDE, + order=EXECUTION_RESULT_ORDER, ) if not execution: return None diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index c556ba0450..d3d3ae8a02 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -655,7 +655,10 @@ async def get_graphs( graph_models = [] for graph in graphs: try: - graph_models.append(GraphModel.from_db(graph)) + graph_model = GraphModel.from_db(graph) + # Trigger serialization to validate that the graph is well formed. + graph_model.model_dump() + graph_models.append(graph_model) except Exception as e: logger.error(f"Error processing graph {graph.id}: {e}") continue @@ -1082,7 +1085,7 @@ async def fix_llm_provider_credentials(): ) continue - store.update_creds(user_id, credentials) + await store.update_creds(user_id, credentials) await AgentNode.prisma().update( where={"id": node_id}, data={"constantInput": Json(node_preset_input)}, diff --git a/autogpt_platform/backend/backend/data/includes.py b/autogpt_platform/backend/backend/data/includes.py index d763579d91..347bb9ef91 100644 --- a/autogpt_platform/backend/backend/data/includes.py +++ b/autogpt_platform/backend/backend/data/includes.py @@ -14,9 +14,15 @@ AGENT_GRAPH_INCLUDE: prisma.types.AgentGraphInclude = { "Nodes": {"include": AGENT_NODE_INCLUDE} } +EXECUTION_RESULT_ORDER: list[prisma.types.AgentNodeExecutionOrderByInput] = [ + {"queuedTime": "desc"}, + # Fallback: Incomplete execs has no queuedTime. + {"addedTime": "desc"}, +] + EXECUTION_RESULT_INCLUDE: prisma.types.AgentNodeExecutionInclude = { - "Input": True, - "Output": True, + "Input": {"order_by": {"time": "asc"}}, + "Output": {"order_by": {"time": "asc"}}, "Node": True, "GraphExecution": True, } @@ -25,17 +31,8 @@ MAX_NODE_EXECUTIONS_FETCH = 1000 GRAPH_EXECUTION_INCLUDE_WITH_NODES: prisma.types.AgentGraphExecutionInclude = { "NodeExecutions": { - "include": { - "Input": True, - "Output": True, - "Node": True, - "GraphExecution": True, - }, - "order_by": [ - {"queuedTime": "desc"}, - # Fallback: Incomplete execs has no queuedTime. - {"addedTime": "desc"}, - ], + "include": EXECUTION_RESULT_INCLUDE, + "order_by": EXECUTION_RESULT_ORDER, "take": MAX_NODE_EXECUTIONS_FETCH, # Avoid loading excessive node executions. } } diff --git a/autogpt_platform/backend/backend/data/notifications.py b/autogpt_platform/backend/backend/data/notifications.py index d0f49b7021..f575081be7 100644 --- a/autogpt_platform/backend/backend/data/notifications.py +++ b/autogpt_platform/backend/backend/data/notifications.py @@ -16,10 +16,11 @@ from prisma.types import ( from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator from backend.server.v2.store.exceptions import DatabaseError +from backend.util.logging import TruncatedLogger from .db import transaction -logger = logging.getLogger(__name__) +logger = TruncatedLogger(logging.getLogger(__name__), prefix="[NotificationService]") NotificationDataType_co = TypeVar( @@ -111,7 +112,14 @@ class BaseSummaryData(BaseNotificationData): class BaseSummaryParams(BaseModel): - pass + start_date: datetime + end_date: datetime + + @field_validator("start_date", "end_date") + def validate_timezone(cls, value): + if value.tzinfo is None: + raise ValueError("datetime must have timezone information") + return value class DailySummaryParams(BaseSummaryParams): diff --git a/autogpt_platform/backend/backend/data/redis.py b/autogpt_platform/backend/backend/data/redis.py index 36410fe29c..c6225131f2 100644 --- a/autogpt_platform/backend/backend/data/redis.py +++ b/autogpt_platform/backend/backend/data/redis.py @@ -1,6 +1,8 @@ import logging import os +from functools import cache +from autogpt_libs.utils.cache import thread_cached from dotenv import load_dotenv from redis import Redis from redis.asyncio import Redis as AsyncRedis @@ -14,16 +16,10 @@ PORT = int(os.getenv("REDIS_PORT", "6379")) PASSWORD = os.getenv("REDIS_PASSWORD", "password") logger = logging.getLogger(__name__) -connection: Redis | None = None -connection_async: AsyncRedis | None = None @conn_retry("Redis", "Acquiring connection") def connect() -> Redis: - global connection - if connection: - return connection - c = Redis( host=HOST, port=PORT, @@ -31,32 +27,21 @@ def connect() -> Redis: decode_responses=True, ) c.ping() - connection = c - return connection + return c @conn_retry("Redis", "Releasing connection") def disconnect(): - global connection - if connection: - connection.close() - connection = None + get_redis().close() -def get_redis(auto_connect: bool = True) -> Redis: - if connection: - return connection - if auto_connect: - return connect() - raise RuntimeError("Redis connection is not established") +@cache +def get_redis() -> Redis: + return connect() @conn_retry("AsyncRedis", "Acquiring connection") async def connect_async() -> AsyncRedis: - global connection_async - if connection_async: - return connection_async - c = AsyncRedis( host=HOST, port=PORT, @@ -64,21 +49,15 @@ async def connect_async() -> AsyncRedis: decode_responses=True, ) await c.ping() - connection_async = c - return connection_async + return c @conn_retry("AsyncRedis", "Releasing connection") async def disconnect_async(): - global connection_async - if connection_async: - await connection_async.close() - connection_async = None + c = await get_redis_async() + await c.close() -async def get_redis_async(auto_connect: bool = True) -> AsyncRedis: - if connection_async: - return connection_async - if auto_connect: - return await connect_async() - raise RuntimeError("AsyncRedis connection is not established") +@thread_cached +async def get_redis_async() -> AsyncRedis: + return await connect_async() diff --git a/autogpt_platform/backend/backend/executor/__init__.py b/autogpt_platform/backend/backend/executor/__init__.py index a92302a62e..92d8b5dc58 100644 --- a/autogpt_platform/backend/backend/executor/__init__.py +++ b/autogpt_platform/backend/backend/executor/__init__.py @@ -1,10 +1,11 @@ -from .database import DatabaseManager, DatabaseManagerClient +from .database import DatabaseManager, DatabaseManagerAsyncClient, DatabaseManagerClient from .manager import ExecutionManager from .scheduler import Scheduler __all__ = [ "DatabaseManager", "DatabaseManagerClient", + "DatabaseManagerAsyncClient", "ExecutionManager", "Scheduler", ] diff --git a/autogpt_platform/backend/backend/executor/database.py b/autogpt_platform/backend/backend/executor/database.py index e1b4c9c776..81440f3555 100644 --- a/autogpt_platform/backend/backend/executor/database.py +++ b/autogpt_platform/backend/backend/executor/database.py @@ -192,3 +192,26 @@ class DatabaseManagerClient(AppServiceClient): get_user_notification_oldest_message_in_batch = _( d.get_user_notification_oldest_message_in_batch ) + + +class DatabaseManagerAsyncClient(AppServiceClient): + d = DatabaseManager + + @classmethod + def get_service_type(cls): + return DatabaseManager + + create_graph_execution = d.create_graph_execution + get_latest_node_execution = d.get_latest_node_execution + get_graph = d.get_graph + get_node = d.get_node + get_node_execution = d.get_node_execution + get_node_executions = d.get_node_executions + get_user_integrations = d.get_user_integrations + upsert_execution_input = d.upsert_execution_input + upsert_execution_output = d.upsert_execution_output + update_graph_execution_stats = d.update_graph_execution_stats + update_node_execution_stats = d.update_node_execution_stats + update_node_execution_status = d.update_node_execution_status + update_node_execution_status_batch = d.update_node_execution_status_batch + update_user_integrations = d.update_user_integrations diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index d4bf006473..1736539230 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -1,20 +1,18 @@ -import atexit +import asyncio import logging import multiprocessing import os -import signal import sys import threading import time from collections import defaultdict -from concurrent.futures import Future, ProcessPoolExecutor -from contextlib import contextmanager -from multiprocessing.pool import Pool -from typing import TYPE_CHECKING, Optional, TypeVar, cast +from concurrent.futures import CancelledError, Future, ProcessPoolExecutor +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast from pika.adapters.blocking_connection import BlockingChannel from pika.spec import Basic, BasicProperties -from redis.lock import Lock as RedisLock +from redis.asyncio.lock import Lock as RedisLock from backend.blocks.io import AgentOutputBlock from backend.data.model import ( @@ -34,7 +32,7 @@ from backend.notifications.notifications import queue_notification from backend.util.exceptions import InsufficientBalanceError if TYPE_CHECKING: - from backend.executor import DatabaseManagerClient + from backend.executor import DatabaseManagerClient, DatabaseManagerAsyncClient from autogpt_libs.utils.cache import thread_cached from prometheus_client import Gauge, start_http_server @@ -66,6 +64,7 @@ from backend.executor.utils import ( NodeExecutionProgress, block_usage_cost, execution_usage_cost, + get_async_execution_event_bus, get_execution_event_bus, get_execution_queue, parse_execution_output, @@ -73,7 +72,12 @@ from backend.executor.utils import ( ) from backend.integrations.creds_manager import IntegrationCredentialsManager from backend.util import json -from backend.util.decorator import error_logged, time_measured +from backend.util.decorator import ( + async_error_logged, + async_time_measured, + error_logged, + time_measured, +) from backend.util.file import clean_exec_files from backend.util.logging import TruncatedLogger, configure_logging from backend.util.process import AppProcess, set_service_name @@ -81,7 +85,8 @@ from backend.util.retry import continuous_retry, func_retry from backend.util.service import get_service_client from backend.util.settings import Settings -logger = logging.getLogger(__name__) +_logger = logging.getLogger(__name__) +logger = TruncatedLogger(_logger, prefix="[GraphExecutor]") settings = Settings() active_runs_gauge = Gauge( @@ -118,7 +123,7 @@ class LogMetadata(TruncatedLogger): } prefix = f"[ExecutionManager|uid:{user_id}|gid:{graph_id}|nid:{node_id}]|geid:{graph_eid}|neid:{node_eid}|{block_name}]" super().__init__( - logger, + _logger, max_length=max_length, prefix=prefix, metadata=metadata, @@ -128,7 +133,7 @@ class LogMetadata(TruncatedLogger): T = TypeVar("T") -def execute_node( +async def execute_node( node: Node, creds_manager: IntegrationCredentialsManager, data: NodeExecutionEntry, @@ -205,12 +210,14 @@ def execute_node( input_model = cast(type[BlockSchema], node_block.input_schema) for field_name, input_type in input_model.get_credentials_fields().items(): credentials_meta = input_type(**input_data[field_name]) - credentials, creds_lock = creds_manager.acquire(user_id, credentials_meta.id) + credentials, creds_lock = await creds_manager.acquire( + user_id, credentials_meta.id + ) extra_exec_kwargs[field_name] = credentials output_size = 0 try: - for output_name, output_data in node_block.execute( + async for output_name, output_data in node_block.execute( input_data, **extra_exec_kwargs ): output_data = json.convert_pydantic_to_json(output_data) @@ -225,9 +232,9 @@ def execute_node( finally: # Ensure credentials are released even if execution fails - if creds_lock and creds_lock.locked() and creds_lock.owned(): + if creds_lock and (await creds_lock.locked()) and (await creds_lock.owned()): try: - creds_lock.release() + await creds_lock.release() except Exception as e: log_metadata.error(f"Failed to release credentials lock: {e}") @@ -240,8 +247,8 @@ def execute_node( execution_stats.output_size = output_size -def _enqueue_next_nodes( - db_client: "DatabaseManagerClient", +async def _enqueue_next_nodes( + db_client: "DatabaseManagerAsyncClient", node: Node, output: BlockData, user_id: str, @@ -250,10 +257,10 @@ def _enqueue_next_nodes( log_metadata: LogMetadata, node_credentials_input_map: Optional[dict[str, dict[str, CredentialsMetaInput]]], ) -> list[NodeExecutionEntry]: - def add_enqueued_execution( + async def add_enqueued_execution( node_exec_id: str, node_id: str, block_id: str, data: BlockInput ) -> NodeExecutionEntry: - update_node_execution_status( + await async_update_node_execution_status( db_client=db_client, exec_id=node_exec_id, status=ExecutionStatus.QUEUED, @@ -269,37 +276,37 @@ def _enqueue_next_nodes( inputs=data, ) - def register_next_executions(node_link: Link) -> list[NodeExecutionEntry]: + async def register_next_executions(node_link: Link) -> list[NodeExecutionEntry]: try: - return _register_next_executions(node_link) + return await _register_next_executions(node_link) except Exception as e: log_metadata.exception(f"Failed to register next executions: {e}") return [] - def _register_next_executions(node_link: Link) -> list[NodeExecutionEntry]: + async def _register_next_executions(node_link: Link) -> list[NodeExecutionEntry]: enqueued_executions = [] next_output_name = node_link.source_name next_input_name = node_link.sink_name next_node_id = node_link.sink_id + output_name, _ = output next_data = parse_execution_output(output, next_output_name) - if next_data is None: + if next_data is None and output_name != next_output_name: return enqueued_executions - - next_node = db_client.get_node(next_node_id) + next_node = await db_client.get_node(next_node_id) # Multiple node can register the same next node, we need this to be atomic # To avoid same execution to be enqueued multiple times, # Or the same input to be consumed multiple times. - with synchronized(f"upsert_input-{next_node_id}-{graph_exec_id}"): + async with synchronized(f"upsert_input-{next_node_id}-{graph_exec_id}"): # Add output data to the earliest incomplete execution, or create a new one. - next_node_exec_id, next_node_input = db_client.upsert_execution_input( + next_node_exec_id, next_node_input = await db_client.upsert_execution_input( node_id=next_node_id, graph_exec_id=graph_exec_id, input_name=next_input_name, input_data=next_data, ) - update_node_execution_status( + await async_update_node_execution_status( db_client=db_client, exec_id=next_node_exec_id, status=ExecutionStatus.INCOMPLETE, @@ -312,7 +319,7 @@ def _enqueue_next_nodes( if link.is_static and link.sink_name not in next_node_input } if static_link_names and ( - latest_execution := db_client.get_latest_node_execution( + latest_execution := await db_client.get_latest_node_execution( next_node_id, graph_exec_id ) ): @@ -340,7 +347,7 @@ def _enqueue_next_nodes( # Input is complete, enqueue the execution. log_metadata.info(f"Enqueued {suffix}") enqueued_executions.append( - add_enqueued_execution( + await add_enqueued_execution( node_exec_id=next_node_exec_id, node_id=next_node_id, block_id=next_node.block_id, @@ -354,7 +361,7 @@ def _enqueue_next_nodes( # 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. - for iexec in db_client.get_node_executions( + for iexec in await db_client.get_node_executions( node_id=next_node_id, graph_exec_id=graph_exec_id, statuses=[ExecutionStatus.INCOMPLETE], @@ -383,7 +390,7 @@ def _enqueue_next_nodes( continue log_metadata.info(f"Enqueueing static-link execution {suffix}") enqueued_executions.append( - add_enqueued_execution( + await add_enqueued_execution( node_exec_id=iexec.node_exec_id, node_id=next_node_id, block_id=next_node.block_id, @@ -395,7 +402,7 @@ def _enqueue_next_nodes( return [ execution for link in node.output_links - for execution in register_next_executions(link) + for execution in await register_next_executions(link) ] @@ -404,11 +411,9 @@ class Executor: 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. + on_node_execution: Execution logic for a node. The execution flow: 1. Graph execution request is added to the queue. @@ -425,46 +430,11 @@ class Executor: """ @classmethod - @func_retry - def on_node_executor_start(cls): - configure_logging() - set_service_name("NodeExecutor") - redis.connect() - cls.pid = os.getpid() - cls.db_client = get_db_client() - cls.creds_manager = IntegrationCredentialsManager() - - # Set up shutdown handlers - cls.shutdown_lock = threading.Lock() - atexit.register(cls.on_node_executor_stop) - signal.signal(signal.SIGTERM, lambda _, __: cls.on_node_executor_sigterm()) - signal.signal(signal.SIGINT, lambda _, __: cls.on_node_executor_sigterm()) - - @classmethod - def on_node_executor_stop(cls, log=logger.info): - if not cls.shutdown_lock.acquire(blocking=False): - return # already shutting down - - log(f"[on_node_executor_stop {cls.pid}] ⏳ Releasing locks...") - cls.creds_manager.release_all_locks() - log(f"[on_node_executor_stop {cls.pid}] ⏳ Disconnecting Redis...") - redis.disconnect() - log(f"[on_node_executor_stop {cls.pid}] ⏳ Disconnecting DB manager...") - cls.db_client.close() - log(f"[on_node_executor_stop {cls.pid}] ✅ Finished NodeExec cleanup") - sys.exit(0) - - @classmethod - def on_node_executor_sigterm(cls): - llprint(f"[on_node_executor_sigterm {cls.pid}] ⚠️ NodeExec SIGTERM received") - cls.on_node_executor_stop(log=llprint) - - @classmethod - @error_logged - def on_node_execution( + @async_error_logged + async def on_node_execution( cls, - q: ExecutionQueue[ExecutionOutputEntry], node_exec: NodeExecutionEntry, + node_exec_progress: NodeExecutionProgress, node_credentials_input_map: Optional[ dict[str, dict[str, CredentialsMetaInput]] ] = None, @@ -477,13 +447,15 @@ class Executor: node_id=node_exec.node_id, block_name="-", ) - node = cls.db_client.get_node(node_exec.node_id) + db_client = get_db_async_client() + node = await db_client.get_node(node_exec.node_id) execution_stats = NodeExecutionStats() - timing_info, _ = cls._on_node_execution( - q=q, - node_exec=node_exec, + timing_info, _ = await cls._on_node_execution( node=node, + node_exec=node_exec, + node_exec_progress=node_exec_progress, + db_client=db_client, log_metadata=log_metadata, stats=execution_stats, node_credentials_input_map=node_credentials_input_map, @@ -493,19 +465,20 @@ class Executor: if isinstance(execution_stats.error, Exception): execution_stats.error = str(execution_stats.error) - exec_update = cls.db_client.update_node_execution_stats( + exec_update = await db_client.update_node_execution_stats( node_exec.node_exec_id, execution_stats ) - send_execution_update(exec_update) + await send_async_execution_update(exec_update) return execution_stats @classmethod - @time_measured - def _on_node_execution( + @async_time_measured + async def _on_node_execution( cls, - q: ExecutionQueue[ExecutionOutputEntry], - node_exec: NodeExecutionEntry, node: Node, + node_exec: NodeExecutionEntry, + node_exec_progress: NodeExecutionProgress, + db_client: "DatabaseManagerAsyncClient", log_metadata: LogMetadata, stats: NodeExecutionStats | None = None, node_credentials_input_map: Optional[ @@ -514,20 +487,20 @@ class Executor: ): try: log_metadata.info(f"Start node execution {node_exec.node_exec_id}") - update_node_execution_status( - db_client=cls.db_client, + await async_update_node_execution_status( + db_client=db_client, exec_id=node_exec.node_exec_id, status=ExecutionStatus.RUNNING, ) - for output_name, output_data in execute_node( + async for output_name, output_data in execute_node( node=node, creds_manager=cls.creds_manager, data=node_exec, execution_stats=stats, node_credentials_input_map=node_credentials_input_map, ): - q.add( + node_exec_progress.add_output( ExecutionOutputEntry( node=node, node_exec_id=node_exec.node_exec_id, @@ -554,19 +527,19 @@ class Executor: def on_graph_executor_start(cls): configure_logging() set_service_name("GraphExecutor") - - cls.db_client = get_db_client() - cls.pool_size = settings.config.num_node_workers cls.pid = os.getpid() - cls._init_node_executor_pool() - logger.info(f"GraphExec {cls.pid} started with {cls.pool_size} node workers") - - @classmethod - def _init_node_executor_pool(cls): - cls.executor = Pool( - processes=cls.pool_size, - initializer=cls.on_node_executor_start, + cls.creds_manager = IntegrationCredentialsManager() + cls.node_execution_loop = asyncio.new_event_loop() + cls.node_evaluation_loop = asyncio.new_event_loop() + cls.node_execution_thread = threading.Thread( + target=cls.node_execution_loop.run_forever, daemon=True ) + cls.node_evaluation_thread = threading.Thread( + target=cls.node_evaluation_loop.run_forever, daemon=True + ) + cls.node_execution_thread.start() + cls.node_evaluation_thread.start() + logger.info(f"[GraphExecutor] {cls.pid} started") @classmethod @error_logged @@ -581,8 +554,9 @@ class Executor: node_eid="*", block_name="-", ) + db_client = get_db_client() - exec_meta = cls.db_client.get_graph_execution_meta( + exec_meta = db_client.get_graph_execution_meta( user_id=graph_exec.user_id, execution_id=graph_exec.graph_exec_id, ) @@ -596,9 +570,7 @@ class Executor: log_metadata.info(f"⚙️ Starting graph execution #{graph_exec.graph_exec_id}") exec_meta.status = ExecutionStatus.RUNNING send_execution_update( - cls.db_client.update_graph_execution_start_time( - graph_exec.graph_exec_id - ) + db_client.update_graph_execution_start_time(graph_exec.graph_exec_id) ) elif exec_meta.status == ExecutionStatus.RUNNING: log_metadata.info( @@ -622,14 +594,14 @@ class Executor: exec_stats.cputime += timing_info.cpu_time exec_stats.error = str(error) if error else exec_stats.error - if graph_exec_result := cls.db_client.update_graph_execution_stats( + if graph_exec_result := db_client.update_graph_execution_stats( graph_exec_id=graph_exec.graph_exec_id, status=status, stats=exec_stats, ): send_execution_update(graph_exec_result) - cls._handle_agent_run_notif(graph_exec, exec_stats) + cls._handle_agent_run_notif(db_client, graph_exec, exec_stats) @classmethod def _charge_usage( @@ -638,6 +610,7 @@ class Executor: execution_count: int, execution_stats: GraphExecutionStats, ): + db_client = get_db_client() block = get_block(node_exec.block_id) if not block: logger.error(f"Block {node_exec.block_id} not found.") @@ -647,7 +620,7 @@ class Executor: block=block, input_data=node_exec.inputs ) if cost > 0: - cls.db_client.spend_credits( + db_client.spend_credits( user_id=node_exec.user_id, cost=cost, metadata=UsageTransactionMetadata( @@ -665,7 +638,7 @@ class Executor: cost, usage_count = execution_usage_cost(execution_count) if cost > 0: - cls.db_client.spend_credits( + db_client.spend_credits( user_id=node_exec.user_id, cost=cost, metadata=UsageTransactionMetadata( @@ -695,18 +668,11 @@ class Executor: ExecutionStatus: The final status of the graph execution. Exception | None: The error that occurred during the execution, if any. """ - execution_status = ExecutionStatus.RUNNING - error = None - finished = False + execution_status: ExecutionStatus = ExecutionStatus.RUNNING + error: Exception | None = None + db_client = get_db_client() - def drain_output_queue(): - while output := output_queue.get_or_none(): - log_metadata.debug( - f"Received output for {output.node.id} - {output.node_exec_id}: {output.data}" - ) - running_executions[output.node.id].add_output(output) - - def drain_done_task(node_exec_id: str, result: object): + def on_done_task(node_exec_id: str, result: object): if not isinstance(result, NodeExecutionStats): log_metadata.error(f"Unexpected result #{node_exec_id}: {type(result)}") return @@ -718,49 +684,40 @@ class Executor: if (err := result.error) and isinstance(err, Exception): execution_stats.node_error_count += 1 update_node_execution_status( - db_client=cls.db_client, + db_client=db_client, exec_id=node_exec_id, status=ExecutionStatus.FAILED, ) else: update_node_execution_status( - db_client=cls.db_client, + db_client=db_client, exec_id=node_exec_id, status=ExecutionStatus.COMPLETED, ) - if _graph_exec := cls.db_client.update_graph_execution_stats( + if _graph_exec := db_client.update_graph_execution_stats( graph_exec_id=graph_exec.graph_exec_id, status=execution_status, stats=execution_stats, ): send_execution_update(_graph_exec) else: - logger.error( - "Callback for " - f"finished node execution #{node_exec_id} " - "could not update execution stats " + log_metadata.error( + "Callback for finished node execution " + f"#{node_exec_id} could not update execution stats " f"for graph execution #{graph_exec.graph_exec_id}; " f"triggered while graph exec status = {execution_status}" ) - def cancel_handler(): - nonlocal execution_status - - while not cancel.is_set(): - cancel.wait(1) - if finished: - return - execution_status = ExecutionStatus.TERMINATED - cls.executor.terminate() - log_metadata.info(f"Terminated graph execution {graph_exec.graph_exec_id}") - cls._init_node_executor_pool() - - cancel_thread = threading.Thread(target=cancel_handler) - cancel_thread.start() + # State holders ---------------------------------------------------- + running_node_execution: dict[str, NodeExecutionProgress] = defaultdict( + lambda: NodeExecutionProgress(on_done_task=on_done_task) + ) + running_node_evaluation: dict[str, Future] = {} + execution_queue = ExecutionQueue[NodeExecutionEntry]() try: - if cls.db_client.get_credits(graph_exec.user_id) <= 0: + if db_client.get_credits(graph_exec.user_id) <= 0: raise InsufficientBalanceError( user_id=graph_exec.user_id, message="You have no credits left to run an agent.", @@ -768,21 +725,18 @@ class Executor: amount=1, ) - output_queue = ExecutionQueue[ExecutionOutputEntry]() - execution_queue = ExecutionQueue[NodeExecutionEntry]() - for node_exec in cls.db_client.get_node_executions( + # ------------------------------------------------------------ + # Pre‑populate queue --------------------------------------- + # ------------------------------------------------------------ + for node_exec in db_client.get_node_executions( graph_exec.graph_exec_id, statuses=[ExecutionStatus.RUNNING, ExecutionStatus.QUEUED], ): execution_queue.add(node_exec.to_node_execution_entry()) - running_executions: dict[str, NodeExecutionProgress] = defaultdict( - lambda: NodeExecutionProgress( - drain_output_queue=drain_output_queue, - drain_done_task=drain_done_task, - ) - ) - + # ------------------------------------------------------------ + # Main dispatch / polling loop ----------------------------- + # ------------------------------------------------------------ while not execution_queue.empty(): if cancel.is_set(): execution_status = ExecutionStatus.TERMINATED @@ -795,6 +749,7 @@ class Executor: f"for node {queued_node_exec.node_id}", ) + # Charge usage (may raise) ------------------------------ try: cls._charge_usage( node_exec=queued_node_exec, @@ -803,19 +758,20 @@ class Executor: ) except InsufficientBalanceError as error: node_exec_id = queued_node_exec.node_exec_id - cls.db_client.upsert_execution_output( + db_client.upsert_execution_output( node_exec_id=node_exec_id, output_name="error", output_data=str(error), ) update_node_execution_status( - db_client=cls.db_client, + db_client=db_client, exec_id=node_exec_id, status=ExecutionStatus.FAILED, ) execution_status = ExecutionStatus.FAILED cls._handle_low_balance_notif( + db_client, graph_exec.user_id, graph_exec.graph_id, execution_stats, @@ -823,7 +779,7 @@ class Executor: ) raise - # Add credentials input overrides + # Add credential overrides ----------------------------- node_id = queued_node_exec.node_id if (node_creds_map := graph_exec.node_credentials_input_map) and ( node_field_creds_map := node_creds_map.get(node_id) @@ -835,72 +791,120 @@ class Executor: } ) - # Initiate node execution - running_executions[queued_node_exec.node_id].add_task( - queued_node_exec.node_exec_id, - cls.executor.apply_async( - cls.on_node_execution, - (output_queue, queued_node_exec, node_creds_map), + # Kick off async node execution ------------------------- + node_execution_task = asyncio.run_coroutine_threadsafe( + cls.on_node_execution( + node_exec=queued_node_exec, + node_exec_progress=running_node_execution[node_id], + node_credentials_input_map=node_creds_map, ), + cls.node_execution_loop, + ) + running_node_execution[node_id].add_task( + node_exec_id=queued_node_exec.node_exec_id, + task=node_execution_task, ) - # Avoid terminating graph execution when some nodes are still running. - while execution_queue.empty() and running_executions: - log_metadata.debug( - f"Queue empty; running nodes: {list(running_executions.keys())}" - ) - - # Register next node executions from running_executions. - for node_id, execution in list(running_executions.items()): + # Poll until queue refills or all inflight work done ---- + while execution_queue.empty() and ( + running_node_execution or running_node_evaluation + ): + # -------------------------------------------------- + # Handle inflight evaluations --------------------- + # -------------------------------------------------- + node_output_found = False + for node_id, inflight_exec in list(running_node_execution.items()): if cancel.is_set(): execution_status = ExecutionStatus.TERMINATED return execution_stats, execution_status, error - log_metadata.debug(f"Waiting on execution of node {node_id}") - while output := execution.pop_output(): - cls._process_node_output( - output=output, - node_id=node_id, - graph_exec=graph_exec, - log_metadata=log_metadata, - node_creds_map=node_creds_map, - execution_queue=execution_queue, + # node evaluation future ----------------- + if inflight_eval := running_node_evaluation.get(node_id): + try: + inflight_eval.result() + running_node_evaluation.pop(node_id) + except TimeoutError: + continue + + # node execution future --------------------------- + if inflight_exec.is_done(): + running_node_execution.pop(node_id) + continue + + if output := inflight_exec.pop_output(): + node_output_found = True + running_node_evaluation[node_id] = ( + asyncio.run_coroutine_threadsafe( + cls._process_node_output( + output=output, + node_id=node_id, + graph_exec=graph_exec, + log_metadata=log_metadata, + node_creds_map=node_creds_map, + execution_queue=execution_queue, + ), + cls.node_evaluation_loop, + ) ) - if not execution_queue.empty(): - break # Prioritize executing next nodes than enqueuing outputs - - if execution.is_done(): - running_executions.pop(node_id) - - if not execution_queue.empty(): - continue # Make sure each not is checked once - - if execution_queue.empty() and running_executions: - log_metadata.debug( - "No more nodes to execute, waiting for outputs..." - ) + if ( + not node_output_found + and execution_queue.empty() + and (running_node_execution or running_node_evaluation) + ): + # There is nothing to execute, and no output to process, let's relax for a while. time.sleep(0.1) - log_metadata.info(f"Finished graph execution {graph_exec.graph_exec_id}") + # loop done -------------------------------------------------- execution_status = ExecutionStatus.COMPLETED + return execution_stats, execution_status, error - except Exception as e: - error = e + except CancelledError as exc: + execution_status = ExecutionStatus.TERMINATED + error = exc + log_metadata.exception( + f"Cancelled graph execution {graph_exec.graph_exec_id}: {error}" + ) + except Exception as exc: + execution_status = ExecutionStatus.FAILED + error = exc log_metadata.exception( f"Failed graph execution {graph_exec.graph_exec_id}: {error}" ) - execution_status = ExecutionStatus.FAILED - finally: - if not cancel.is_set(): - finished = True - cancel.set() - cancel_thread.join() + for node_id, inflight_exec in running_node_execution.items(): + if inflight_exec.is_done(): + continue + log_metadata.info(f"Stopping node execution {node_id}") + inflight_exec.stop() + + for node_id, inflight_eval in running_node_evaluation.items(): + if inflight_eval.done(): + continue + log_metadata.info(f"Stopping node evaluation {node_id}") + inflight_eval.cancel() + + if execution_status in [ExecutionStatus.TERMINATED, ExecutionStatus.FAILED]: + inflight_executions = db_client.get_node_executions( + graph_exec.graph_exec_id, + statuses=[ + ExecutionStatus.QUEUED, + ExecutionStatus.RUNNING, + ], + ) + db_client.update_node_execution_status_batch( + [node_exec.node_exec_id for node_exec in inflight_executions], + status=execution_status, + stats={"error": str(error)} if error else None, + ) + for node_exec in inflight_executions: + node_exec.status = execution_status + send_execution_update(node_exec) + clean_exec_files(graph_exec.graph_exec_id) return execution_stats, execution_status, error @classmethod - def _process_node_output( + async def _process_node_output( cls, output: ExecutionOutputEntry, node_id: str, @@ -919,19 +923,21 @@ class Executor: node_creds_map: Optional map of node credentials execution_queue: Queue to add next executions to """ + db_client = get_db_async_client() + try: name, data = output.data - cls.db_client.upsert_execution_output( + await db_client.upsert_execution_output( node_exec_id=output.node_exec_id, output_name=name, output_data=data, ) - if exec_update := cls.db_client.get_node_execution(output.node_exec_id): - send_execution_update(exec_update) + if exec_update := await db_client.get_node_execution(output.node_exec_id): + await send_async_execution_update(exec_update) log_metadata.debug(f"Enqueue nodes for {node_id}: {output}") - for next_execution in _enqueue_next_nodes( - db_client=cls.db_client, + for next_execution in await _enqueue_next_nodes( + db_client=db_client, node=output.node, output=output.data, user_id=graph_exec.user_id, @@ -943,13 +949,13 @@ class Executor: execution_queue.add(next_execution) except Exception as e: log_metadata.exception(f"Failed to process node output: {e}") - cls.db_client.upsert_execution_output( + await db_client.upsert_execution_output( node_exec_id=output.node_exec_id, output_name="error", output_data=str(e), ) - update_node_execution_status( - db_client=cls.db_client, + await async_update_node_execution_status( + db_client=db_client, exec_id=output.node_exec_id, status=ExecutionStatus.FAILED, ) @@ -957,13 +963,14 @@ class Executor: @classmethod def _handle_agent_run_notif( cls, + db_client: "DatabaseManagerClient", graph_exec: GraphExecutionEntry, exec_stats: GraphExecutionStats, ): - metadata = cls.db_client.get_graph_metadata( + metadata = db_client.get_graph_metadata( graph_exec.graph_id, graph_exec.graph_version ) - outputs = cls.db_client.get_node_executions( + outputs = db_client.get_node_executions( graph_exec.graph_exec_id, block_ids=[AgentOutputBlock().id], ) @@ -994,13 +1001,14 @@ class Executor: @classmethod def _handle_low_balance_notif( cls, + db_client: "DatabaseManagerClient", user_id: str, graph_id: str, exec_stats: GraphExecutionStats, e: InsufficientBalanceError, ): shortfall = e.balance - e.amount - metadata = cls.db_client.get_graph_metadata(graph_id) + metadata = db_client.get_graph_metadata(graph_id) base_url = ( settings.config.frontend_base_url or settings.config.platform_base_url ) @@ -1046,9 +1054,6 @@ class ExecutionManager(AppProcess): initializer=Executor.on_graph_executor_start, ) - logger.info(f"[{self.service_name}] ⏳ Connecting to Redis...") - redis.connect() - threading.Thread( target=lambda: self._consume_execution_cancel(), daemon=True, @@ -1162,19 +1167,21 @@ class ExecutionManager(AppProcess): self.active_graph_runs.pop(graph_exec_id, None) active_runs_gauge.set(len(self.active_graph_runs)) utilization_gauge.set(len(self.active_graph_runs) / self.pool_size) - if f.exception(): + if exec_error := f.exception(): logger.error( - f"[{self.service_name}] Execution for {graph_exec_id} failed: {f.exception()}" + f"[{self.service_name}] Execution for {graph_exec_id} failed: {exec_error}" ) channel.connection.add_callback_threadsafe( - lambda: channel.basic_nack(delivery_tag, requeue=False) + lambda: channel.basic_nack(delivery_tag, requeue=True) ) else: channel.connection.add_callback_threadsafe( lambda: channel.basic_ack(delivery_tag) ) - except Exception as e: - logger.error(f"[{self.service_name}] Error acknowledging message: {e}") + except BaseException as e: + logger.exception( + f"[{self.service_name}] Error acknowledging message: {e}" + ) future.add_done_callback(_on_run_done) @@ -1209,7 +1216,27 @@ def get_db_client() -> "DatabaseManagerClient": from backend.executor import DatabaseManagerClient # Disable health check for the service client to avoid breaking process initializer. - return get_service_client(DatabaseManagerClient, health_check=False) + return get_service_client( + DatabaseManagerClient, health_check=False, request_retry=True + ) + + +@thread_cached +def get_db_async_client() -> "DatabaseManagerAsyncClient": + from backend.executor import DatabaseManagerAsyncClient + + # Disable health check for the service client to avoid breaking process initializer. + return get_service_client( + DatabaseManagerAsyncClient, health_check=False, request_retry=True + ) + + +async def send_async_execution_update( + entry: GraphExecution | NodeExecutionResult | None, +) -> None: + if entry is None: + return + await get_async_execution_event_bus().publish(entry) def send_execution_update(entry: GraphExecution | NodeExecutionResult | None): @@ -1218,29 +1245,46 @@ def send_execution_update(entry: GraphExecution | NodeExecutionResult | None): return get_execution_event_bus().publish(entry) +async def async_update_node_execution_status( + db_client: "DatabaseManagerAsyncClient", + exec_id: str, + status: ExecutionStatus, + execution_data: BlockInput | None = None, + stats: dict[str, Any] | None = None, +) -> NodeExecutionResult: + """Sets status and fetches+broadcasts the latest state of the node execution""" + exec_update = await db_client.update_node_execution_status( + exec_id, status, execution_data, stats + ) + await send_async_execution_update(exec_update) + return exec_update + + def update_node_execution_status( db_client: "DatabaseManagerClient", exec_id: str, status: ExecutionStatus, execution_data: BlockInput | None = None, + stats: dict[str, Any] | None = None, ) -> NodeExecutionResult: """Sets status and fetches+broadcasts the latest state of the node execution""" exec_update = db_client.update_node_execution_status( - exec_id, status, execution_data + exec_id, status, execution_data, stats ) send_execution_update(exec_update) return exec_update -@contextmanager -def synchronized(key: str, timeout: int = 60): - lock: RedisLock = redis.get_redis().lock(f"lock:{key}", timeout=timeout) +@asynccontextmanager +async def synchronized(key: str, timeout: int = 60): + r = await redis.get_redis_async() + lock: RedisLock = r.lock(f"lock:{key}", timeout=timeout) try: - lock.acquire() + await lock.acquire() yield finally: - if lock.locked() and lock.owned(): - lock.release() + if await lock.locked() and await lock.owned(): + await lock.release() def increment_execution_count(user_id: str) -> int: diff --git a/autogpt_platform/backend/backend/executor/scheduler.py b/autogpt_platform/backend/backend/executor/scheduler.py index 6b9008d4cc..0c25e58f94 100644 --- a/autogpt_platform/backend/backend/executor/scheduler.py +++ b/autogpt_platform/backend/backend/executor/scheduler.py @@ -1,3 +1,4 @@ +import asyncio import logging import os from datetime import datetime, timedelta, timezone @@ -71,15 +72,25 @@ def get_notification_client(): return get_service_client(NotificationManagerClient) +@thread_cached +def get_event_loop(): + return asyncio.new_event_loop() + + def execute_graph(**kwargs): + get_event_loop().run_until_complete(_execute_graph(**kwargs)) + + +async def _execute_graph(**kwargs): args = GraphExecutionJobArgs(**kwargs) try: log(f"Executing recurring job for graph #{args.graph_id}") - execution_utils.add_graph_execution( + await execution_utils.add_graph_execution( graph_id=args.graph_id, inputs=args.input_data, user_id=args.user_id, graph_version=args.graph_version, + use_db_query=False, ) except Exception as e: logger.exception(f"Error executing graph {args.graph_id}: {e}") @@ -104,11 +115,18 @@ def report_late_executions() -> str: num_late_executions = len(late_executions) num_users = len(set([r.user_id for r in late_executions])) + + late_execution_details = [ + f"* `Execution ID: {exec.id}, Graph ID: {exec.graph_id}v{exec.graph_version}, User ID: {exec.user_id}, Created At: {exec.started_at.isoformat()}`" + for exec in late_executions + ] + error = LateExecutionException( f"Late executions detected: {num_late_executions} late executions from {num_users} users " f"in the last {config.execution_late_notification_checkrange_secs} seconds. " f"Graph has been queued for more than {config.execution_late_notification_threshold_secs} seconds. " - "Please check the executor status." + "Please check the executor status. Details:\n" + + "\n".join(late_execution_details) ) msg = str(error) sentry_capture_error(error) diff --git a/autogpt_platform/backend/backend/executor/utils.py b/autogpt_platform/backend/backend/executor/utils.py index 2129632c2a..4ef18d509f 100644 --- a/autogpt_platform/backend/backend/executor/utils.py +++ b/autogpt_platform/backend/backend/executor/utils.py @@ -1,6 +1,7 @@ +import asyncio import logging from collections import defaultdict -from multiprocessing.pool import AsyncResult +from concurrent.futures import Future from typing import TYPE_CHECKING, Any, Callable, Optional, cast from autogpt_libs.utils.cache import thread_cached @@ -23,6 +24,7 @@ from backend.data.execution import ( GraphExecutionWithNodes, RedisExecutionEventBus, create_graph_execution, + get_node_executions, update_graph_execution_stats, update_node_execution_status_batch, ) @@ -37,17 +39,18 @@ from backend.data.rabbitmq import ( SyncRabbitMQ, ) from backend.util.exceptions import NotFoundError +from backend.util.logging import TruncatedLogger from backend.util.mock import MockObject from backend.util.service import get_service_client from backend.util.settings import Config from backend.util.type import convert if TYPE_CHECKING: - from backend.executor import DatabaseManagerClient + from backend.executor import DatabaseManagerAsyncClient, DatabaseManagerClient from backend.integrations.credentials_store import IntegrationCredentialsStore config = Config() -logger = logging.getLogger(__name__) +logger = TruncatedLogger(logging.getLogger(__name__), prefix="[GraphExecutorUtil]") # ============ Resource Helpers ============ # @@ -90,6 +93,13 @@ def get_db_client() -> "DatabaseManagerClient": return get_service_client(DatabaseManagerClient) +@thread_cached +def get_db_async_client() -> "DatabaseManagerAsyncClient": + from backend.executor import DatabaseManagerAsyncClient + + return get_service_client(DatabaseManagerAsyncClient) + + # ============ Execution Cost Helpers ============ # @@ -392,12 +402,6 @@ def validate_exec( return None, f"Block for {node.block_id} not found." schema = node_block.input_schema - # Convert non-matching data types to the expected input schema. - for name, data_type in schema.__annotations__.items(): - value = data.get(name) - if (value is not None) and (type(value) is not data_type): - data[name] = convert(value, data_type) - # Input data (without default values) should contain all required fields. error_prefix = f"Input data missing or mismatch for `{node_block.name}`:" if missing_links := schema.get_missing_links(data, node.input_links): @@ -409,6 +413,12 @@ def validate_exec( if resolve_input: data = merge_execution_input(data) + # Convert non-matching data types to the expected input schema. + for name, data_type in schema.__annotations__.items(): + value = data.get(name) + if (value is not None) and (type(value) is not data_type): + data[name] = convert(value, data_type) + # Input data post-merge should contain all required fields from the schema. if missing_input := schema.get_missing_input(data): return None, f"{error_prefix} missing input {missing_input}" @@ -422,7 +432,7 @@ def validate_exec( return data, node_block.name -def _validate_node_input_credentials( +async def _validate_node_input_credentials( graph: GraphModel, user_id: str, node_credentials_input_map: Optional[ @@ -459,7 +469,7 @@ def _validate_node_input_credentials( ) # Fetch the corresponding Credentials and perform sanity checks - credentials = get_integration_credentials_store().get_creds_by_id( + credentials = await get_integration_credentials_store().get_creds_by_id( user_id, credentials_meta.id ) if not credentials: @@ -516,7 +526,7 @@ def make_node_credentials_input_map( return result -def construct_node_execution_input( +async def construct_node_execution_input( graph: GraphModel, user_id: str, graph_inputs: BlockInput, @@ -541,7 +551,7 @@ def construct_node_execution_input( the corresponding input data for that node. """ graph.validate_graph(for_run=True) - _validate_node_input_credentials(graph, user_id, node_credentials_input_map) + await _validate_node_input_credentials(graph, user_id, node_credentials_input_map) nodes_input = [] for node in graph.starting_nodes: @@ -642,13 +652,92 @@ def create_execution_queue_config() -> RabbitMQConfig: ) -async def add_graph_execution_async( +async def stop_graph_execution( + graph_exec_id: str, + use_db_query: bool = True, +): + """ + Mechanism: + 1. Set the cancel event + 2. Graph executor's cancel handler thread detects the event, terminates workers, + reinitializes worker pool, and returns. + 3. Update execution statuses in DB and set `error` outputs to `"TERMINATED"`. + """ + queue_client = await get_async_execution_queue() + await queue_client.publish_message( + routing_key="", + message=CancelExecutionEvent(graph_exec_id=graph_exec_id).model_dump_json(), + exchange=GRAPH_EXECUTION_CANCEL_EXCHANGE, + ) + + # Update the status of the graph execution + if use_db_query: + graph_execution = await update_graph_execution_stats( + graph_exec_id, + ExecutionStatus.TERMINATED, + ) + else: + graph_execution = await get_db_async_client().update_graph_execution_stats( + graph_exec_id, + ExecutionStatus.TERMINATED, + ) + + if graph_execution: + await get_async_execution_event_bus().publish(graph_execution) + else: + raise NotFoundError( + f"Graph execution #{graph_exec_id} not found for termination." + ) + + # Update the status of the node executions + if use_db_query: + node_executions = await get_node_executions( + graph_exec_id=graph_exec_id, + statuses=[ + ExecutionStatus.QUEUED, + ExecutionStatus.RUNNING, + ExecutionStatus.INCOMPLETE, + ], + ) + await update_node_execution_status_batch( + [v.node_exec_id for v in node_executions], + ExecutionStatus.TERMINATED, + ) + else: + node_executions = await get_db_async_client().get_node_executions( + graph_exec_id=graph_exec_id, + statuses=[ + ExecutionStatus.QUEUED, + ExecutionStatus.RUNNING, + ExecutionStatus.INCOMPLETE, + ], + ) + await get_db_async_client().update_node_execution_status_batch( + [v.node_exec_id for v in node_executions], + ExecutionStatus.TERMINATED, + ) + + await asyncio.gather( + *[ + get_async_execution_event_bus().publish( + v.model_copy(update={"status": ExecutionStatus.TERMINATED}) + ) + for v in node_executions + ] + ) + + +async def add_graph_execution( graph_id: str, user_id: str, inputs: BlockInput, preset_id: Optional[str] = None, graph_version: Optional[int] = None, graph_credentials_inputs: Optional[dict[str, CredentialsMetaInput]] = None, + node_credentials_input_map: Optional[ + dict[str, dict[str, CredentialsMetaInput]] + ] = None, + use_db_query: bool = True, ) -> GraphExecutionWithNodes: """ Adds a graph execution to the queue and returns the execution entry. @@ -661,38 +750,63 @@ async def add_graph_execution_async( graph_version: The version of the graph to execute. graph_credentials_inputs: Credentials inputs to use in the execution. Keys should map to the keys generated by `GraphModel.aggregate_credentials_inputs`. + node_credentials_input_map: Credentials inputs to use in the execution, mapped to specific nodes. Returns: GraphExecutionEntry: The entry for the graph execution. Raises: ValueError: If the graph is not found or if there are validation errors. """ # noqa - graph: GraphModel | None = await get_graph( - graph_id=graph_id, - user_id=user_id, - version=graph_version, - include_subgraphs=True, - ) + if use_db_query: + graph: GraphModel | None = await get_graph( + graph_id=graph_id, + user_id=user_id, + version=graph_version, + include_subgraphs=True, + ) + else: + graph: GraphModel | None = await get_db_async_client().get_graph( + graph_id=graph_id, + user_id=user_id, + version=graph_version, + include_subgraphs=True, + ) + if not graph: raise NotFoundError(f"Graph #{graph_id} not found.") - node_credentials_input_map = ( + node_credentials_input_map = node_credentials_input_map or ( make_node_credentials_input_map(graph, graph_credentials_inputs) if graph_credentials_inputs else None ) - graph_exec = await create_graph_execution( - user_id=user_id, - graph_id=graph_id, - graph_version=graph.version, - starting_nodes_input=construct_node_execution_input( - graph=graph, + if use_db_query: + graph_exec = await create_graph_execution( user_id=user_id, - graph_inputs=inputs, - node_credentials_input_map=node_credentials_input_map, - ), - preset_id=preset_id, - ) + graph_id=graph_id, + graph_version=graph.version, + starting_nodes_input=await construct_node_execution_input( + graph=graph, + user_id=user_id, + graph_inputs=inputs, + node_credentials_input_map=node_credentials_input_map, + ), + preset_id=preset_id, + ) + else: + graph_exec = await get_db_async_client().create_graph_execution( + user_id=user_id, + graph_id=graph_id, + graph_version=graph.version, + starting_nodes_input=await construct_node_execution_input( + graph=graph, + user_id=user_id, + graph_inputs=inputs, + node_credentials_input_map=node_credentials_input_map, + ), + preset_id=preset_id, + ) + try: queue = await get_async_execution_queue() graph_exec_entry = graph_exec.to_graph_execution_entry() @@ -711,101 +825,27 @@ async def add_graph_execution_async( except Exception as e: logger.error(f"Unable to publish graph #{graph_id} exec #{graph_exec.id}: {e}") - await update_node_execution_status_batch( - [node_exec.node_exec_id for node_exec in graph_exec.node_executions], - ExecutionStatus.FAILED, - ) - await update_graph_execution_stats( - graph_exec_id=graph_exec.id, - status=ExecutionStatus.FAILED, - stats=GraphExecutionStats(error=str(e)), - ) - raise + if use_db_query: + await update_node_execution_status_batch( + [node_exec.node_exec_id for node_exec in graph_exec.node_executions], + ExecutionStatus.FAILED, + ) + await update_graph_execution_stats( + graph_exec_id=graph_exec.id, + status=ExecutionStatus.FAILED, + stats=GraphExecutionStats(error=str(e)), + ) + else: + await get_db_async_client().update_node_execution_status_batch( + [node_exec.node_exec_id for node_exec in graph_exec.node_executions], + ExecutionStatus.FAILED, + ) + await get_db_async_client().update_graph_execution_stats( + graph_exec_id=graph_exec.id, + status=ExecutionStatus.FAILED, + stats=GraphExecutionStats(error=str(e)), + ) - -def add_graph_execution( - graph_id: str, - user_id: str, - inputs: BlockInput, - preset_id: Optional[str] = None, - graph_version: Optional[int] = None, - graph_credentials_inputs: Optional[dict[str, CredentialsMetaInput]] = None, - node_credentials_input_map: Optional[ - dict[str, dict[str, CredentialsMetaInput]] - ] = None, -) -> GraphExecutionWithNodes: - """ - Adds a graph execution to the queue and returns the execution entry. - - Args: - graph_id: The ID of the graph to execute. - user_id: The ID of the user executing the graph. - inputs: The input data for the graph execution. - preset_id: The ID of the preset to use. - graph_version: The version of the graph to execute. - graph_credentials_inputs: Credentials inputs to use in the execution. - Keys should map to the keys generated by `GraphModel.aggregate_credentials_inputs`. - node_credentials_input_map: Credentials inputs to use in the execution, mapped to specific nodes. - Returns: - GraphExecutionEntry: The entry for the graph execution. - Raises: - ValueError: If the graph is not found or if there are validation errors. - """ - db = get_db_client() - graph: GraphModel | None = db.get_graph( - graph_id=graph_id, - user_id=user_id, - version=graph_version, - include_subgraphs=True, - ) - if not graph: - raise NotFoundError(f"Graph #{graph_id} not found.") - - node_credentials_input_map = node_credentials_input_map or ( - make_node_credentials_input_map(graph, graph_credentials_inputs) - if graph_credentials_inputs - else None - ) - - graph_exec = db.create_graph_execution( - user_id=user_id, - graph_id=graph_id, - graph_version=graph.version, - starting_nodes_input=construct_node_execution_input( - graph=graph, - user_id=user_id, - graph_inputs=inputs, - node_credentials_input_map=node_credentials_input_map, - ), - preset_id=preset_id, - ) - try: - queue = get_execution_queue() - graph_exec_entry = graph_exec.to_graph_execution_entry() - if node_credentials_input_map: - graph_exec_entry.node_credentials_input_map = node_credentials_input_map - queue.publish_message( - routing_key=GRAPH_EXECUTION_ROUTING_KEY, - message=graph_exec_entry.model_dump_json(), - exchange=GRAPH_EXECUTION_EXCHANGE, - ) - - bus = get_execution_event_bus() - bus.publish(graph_exec) - - return graph_exec - except Exception as e: - logger.error(f"Unable to publish graph #{graph_id} exec #{graph_exec.id}: {e}") - - db.update_node_execution_status_batch( - [node_exec.node_exec_id for node_exec in graph_exec.node_executions], - ExecutionStatus.FAILED, - ) - db.update_graph_execution_stats( - graph_exec_id=graph_exec.id, - status=ExecutionStatus.FAILED, - stats=GraphExecutionStats(error=str(e)), - ) raise @@ -821,15 +861,13 @@ class ExecutionOutputEntry(BaseModel): class NodeExecutionProgress: def __init__( self, - drain_output_queue: Callable[[], None], - drain_done_task: Callable[[str, object], None], + on_done_task: Callable[[str, object], None], ): self.output: dict[str, list[ExecutionOutputEntry]] = defaultdict(list) - self.tasks: dict[str, AsyncResult] = {} - self.drain_output_queue = drain_output_queue - self.drain_done_task = drain_done_task + self.tasks: dict[str, Future] = {} + self.on_done_task = on_done_task - def add_task(self, node_exec_id: str, task: AsyncResult): + def add_task(self, node_exec_id: str, task: Future): self.tasks[node_exec_id] = task def add_output(self, output: ExecutionOutputEntry): @@ -859,23 +897,46 @@ class NodeExecutionProgress: if wait_time <= 0: return False - self.tasks[exec_id].wait(wait_time) + try: + self.tasks[exec_id].result(wait_time) + except TimeoutError: + print( + ">>>>>>> -- Timeout, after waiting for", + wait_time, + "seconds for node_id", + exec_id, + ) + pass + return self.is_done(0) + def stop(self) -> list[str]: + """ + Stops all tasks and clears the output. + This is useful for cleaning up when the execution is cancelled or terminated. + Returns a list of execution IDs that were stopped. + """ + cancelled_ids = [] + for task_id, task in self.tasks.items(): + if task.done(): + continue + task.cancel() + cancelled_ids.append(task_id) + return cancelled_ids + def _pop_done_task(self, exec_id: str) -> bool: task = self.tasks.get(exec_id) if not task: return True - if not task.ready(): + if not task.done(): return False - self.drain_output_queue() if self.output[exec_id]: return False if task := self.tasks.pop(exec_id): - self.drain_done_task(exec_id, task.get()) + self.on_done_task(exec_id, task.result()) return True diff --git a/autogpt_platform/backend/backend/integrations/credentials_store.py b/autogpt_platform/backend/backend/integrations/credentials_store.py index 847b20fa6c..a86cc28e92 100644 --- a/autogpt_platform/backend/backend/integrations/credentials_store.py +++ b/autogpt_platform/backend/backend/integrations/credentials_store.py @@ -6,11 +6,13 @@ from typing import TYPE_CHECKING, Optional from pydantic import SecretStr +from backend.data.redis import get_redis_async + if TYPE_CHECKING: - from backend.executor.database import DatabaseManagerClient + from backend.executor.database import DatabaseManagerAsyncClient from autogpt_libs.utils.cache import thread_cached -from autogpt_libs.utils.synchronize import RedisKeyedMutex +from autogpt_libs.utils.synchronize import AsyncRedisKeyedMutex from backend.data.model import ( APIKeyCredentials, @@ -220,31 +222,36 @@ DEFAULT_CREDENTIALS = [ class IntegrationCredentialsStore: def __init__(self): - from backend.data.redis import get_redis + self._locks = None - self.locks = RedisKeyedMutex(get_redis()) + async def locks(self) -> AsyncRedisKeyedMutex: + if self._locks: + return self._locks + + self._locks = AsyncRedisKeyedMutex(await get_redis_async()) + return self._locks @property @thread_cached - def db_manager(self) -> "DatabaseManagerClient": - from backend.executor.database import DatabaseManagerClient + def db_manager(self) -> "DatabaseManagerAsyncClient": + from backend.executor.database import DatabaseManagerAsyncClient from backend.util.service import get_service_client - return get_service_client(DatabaseManagerClient) + return get_service_client(DatabaseManagerAsyncClient) - def add_creds(self, user_id: str, credentials: Credentials) -> None: - with self.locked_user_integrations(user_id): - if self.get_creds_by_id(user_id, credentials.id): + async def add_creds(self, user_id: str, credentials: Credentials) -> None: + async with await self.locked_user_integrations(user_id): + if await self.get_creds_by_id(user_id, credentials.id): raise ValueError( f"Can not re-create existing credentials #{credentials.id} " f"for user #{user_id}" ) - self._set_user_integration_creds( - user_id, [*self.get_all_creds(user_id), credentials] + await self._set_user_integration_creds( + user_id, [*(await self.get_all_creds(user_id)), credentials] ) - def get_all_creds(self, user_id: str) -> list[Credentials]: - users_credentials = self._get_user_integrations(user_id).credentials + async def get_all_creds(self, user_id: str) -> list[Credentials]: + users_credentials = (await self._get_user_integrations(user_id)).credentials all_credentials = users_credentials # These will always be added all_credentials.append(ollama_credentials) @@ -294,21 +301,25 @@ class IntegrationCredentialsStore: all_credentials.append(google_maps_credentials) return all_credentials - def get_creds_by_id(self, user_id: str, credentials_id: str) -> Credentials | None: - all_credentials = self.get_all_creds(user_id) + async def get_creds_by_id( + self, user_id: str, credentials_id: str + ) -> Credentials | None: + all_credentials = await self.get_all_creds(user_id) return next((c for c in all_credentials if c.id == credentials_id), None) - def get_creds_by_provider(self, user_id: str, provider: str) -> list[Credentials]: - credentials = self.get_all_creds(user_id) + async def get_creds_by_provider( + self, user_id: str, provider: str + ) -> list[Credentials]: + credentials = await self.get_all_creds(user_id) return [c for c in credentials if c.provider == provider] - def get_authorized_providers(self, user_id: str) -> list[str]: - credentials = self.get_all_creds(user_id) + async def get_authorized_providers(self, user_id: str) -> list[str]: + credentials = await self.get_all_creds(user_id) return list(set(c.provider for c in credentials)) - def update_creds(self, user_id: str, updated: Credentials) -> None: - with self.locked_user_integrations(user_id): - current = self.get_creds_by_id(user_id, updated.id) + async def update_creds(self, user_id: str, updated: Credentials) -> None: + async with await self.locked_user_integrations(user_id): + current = await self.get_creds_by_id(user_id, updated.id) if not current: raise ValueError( f"Credentials with ID {updated.id} " @@ -336,18 +347,18 @@ class IntegrationCredentialsStore: # Update the credentials updated_credentials_list = [ updated if c.id == updated.id else c - for c in self.get_all_creds(user_id) + for c in await self.get_all_creds(user_id) ] - self._set_user_integration_creds(user_id, updated_credentials_list) + await self._set_user_integration_creds(user_id, updated_credentials_list) - def delete_creds_by_id(self, user_id: str, credentials_id: str) -> None: - with self.locked_user_integrations(user_id): + async def delete_creds_by_id(self, user_id: str, credentials_id: str) -> None: + async with await self.locked_user_integrations(user_id): filtered_credentials = [ - c for c in self.get_all_creds(user_id) if c.id != credentials_id + c for c in await self.get_all_creds(user_id) if c.id != credentials_id ] - self._set_user_integration_creds(user_id, filtered_credentials) + await self._set_user_integration_creds(user_id, filtered_credentials) - def store_state_token( + async def store_state_token( self, user_id: str, provider: str, scopes: list[str], use_pkce: bool = False ) -> tuple[str, str]: token = secrets.token_urlsafe(32) @@ -363,14 +374,14 @@ class IntegrationCredentialsStore: scopes=scopes, ) - with self.locked_user_integrations(user_id): + async with await self.locked_user_integrations(user_id): - user_integrations = self._get_user_integrations(user_id) + user_integrations = await self._get_user_integrations(user_id) oauth_states = user_integrations.oauth_states oauth_states.append(state) user_integrations.oauth_states = oauth_states - self.db_manager.update_user_integrations( + await self.db_manager.update_user_integrations( user_id=user_id, data=user_integrations ) @@ -386,11 +397,11 @@ class IntegrationCredentialsStore: code_challenge = base64.urlsafe_b64encode(sha256_hash).decode("utf-8") return code_challenge.replace("=", ""), code_verifier - def verify_state_token( + async def verify_state_token( self, user_id: str, token: str, provider: str ) -> Optional[OAuthState]: - with self.locked_user_integrations(user_id): - user_integrations = self._get_user_integrations(user_id) + async with await self.locked_user_integrations(user_id): + user_integrations = await self._get_user_integrations(user_id) oauth_states = user_integrations.oauth_states now = datetime.now(timezone.utc) @@ -398,7 +409,7 @@ class IntegrationCredentialsStore: ( state for state in oauth_states - if state.token == token + if secrets.compare_digest(state.token, token) and state.provider == provider and state.expires_at > now.timestamp() ), @@ -409,26 +420,26 @@ class IntegrationCredentialsStore: # Remove the used state oauth_states.remove(valid_state) user_integrations.oauth_states = oauth_states - self.db_manager.update_user_integrations(user_id, user_integrations) + await self.db_manager.update_user_integrations( + user_id, user_integrations + ) return valid_state return None - def _set_user_integration_creds( + async def _set_user_integration_creds( self, user_id: str, credentials: list[Credentials] ) -> None: - integrations = self._get_user_integrations(user_id) + integrations = await self._get_user_integrations(user_id) # Remove default credentials from the list credentials = [c for c in credentials if c not in DEFAULT_CREDENTIALS] integrations.credentials = credentials - self.db_manager.update_user_integrations(user_id, integrations) + await self.db_manager.update_user_integrations(user_id, integrations) - def _get_user_integrations(self, user_id: str) -> UserIntegrations: - integrations: UserIntegrations = self.db_manager.get_user_integrations( - user_id=user_id - ) - return integrations + async def _get_user_integrations(self, user_id: str) -> UserIntegrations: + return await self.db_manager.get_user_integrations(user_id=user_id) - def locked_user_integrations(self, user_id: str): + async def locked_user_integrations(self, user_id: str): key = (f"user:{user_id}", "integrations") - return self.locks.locked(key) + locks = await self.locks() + return locks.locked(key) diff --git a/autogpt_platform/backend/backend/integrations/creds_manager.py b/autogpt_platform/backend/backend/integrations/creds_manager.py index eb5e132503..bacc3a53e9 100644 --- a/autogpt_platform/backend/backend/integrations/creds_manager.py +++ b/autogpt_platform/backend/backend/integrations/creds_manager.py @@ -1,13 +1,13 @@ import logging -from contextlib import contextmanager +from contextlib import asynccontextmanager from datetime import datetime -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Any, Callable, Coroutine -from autogpt_libs.utils.synchronize import RedisKeyedMutex -from redis.lock import Lock as RedisLock +from autogpt_libs.utils.synchronize import AsyncRedisKeyedMutex +from redis.asyncio.lock import Lock as AsyncRedisLock -from backend.data import redis from backend.data.model import Credentials, OAuth2Credentials +from backend.data.redis import get_redis_async from backend.integrations.credentials_store import IntegrationCredentialsStore from backend.integrations.oauth import HANDLERS_BY_NAME from backend.integrations.providers import ProviderName @@ -54,20 +54,26 @@ class IntegrationCredentialsManager: """ def __init__(self): - redis_conn = redis.get_redis() - self._locks = RedisKeyedMutex(redis_conn) self.store = IntegrationCredentialsStore() + self._locks = None - def create(self, user_id: str, credentials: Credentials) -> None: - return self.store.add_creds(user_id, credentials) + async def locks(self) -> AsyncRedisKeyedMutex: + if self._locks: + return self._locks - def exists(self, user_id: str, credentials_id: str) -> bool: - return self.store.get_creds_by_id(user_id, credentials_id) is not None + self._locks = AsyncRedisKeyedMutex(await get_redis_async()) + return self._locks - def get( + async def create(self, user_id: str, credentials: Credentials) -> None: + return await self.store.add_creds(user_id, credentials) + + async def exists(self, user_id: str, credentials_id: str) -> bool: + return (await self.store.get_creds_by_id(user_id, credentials_id)) is not None + + async def get( self, user_id: str, credentials_id: str, lock: bool = True ) -> Credentials | None: - credentials = self.store.get_creds_by_id(user_id, credentials_id) + credentials = await self.store.get_creds_by_id(user_id, credentials_id) if not credentials: return None @@ -78,15 +84,15 @@ class IntegrationCredentialsManager: f"{datetime.fromtimestamp(credentials.access_token_expires_at)}; " f"current time is {datetime.now()}" ) - credentials = self.refresh_if_needed(user_id, credentials, lock) + credentials = await self.refresh_if_needed(user_id, credentials, lock) else: logger.debug(f"Credentials #{credentials.id} never expire") return credentials - def acquire( + async def acquire( self, user_id: str, credentials_id: str - ) -> tuple[Credentials, RedisLock]: + ) -> tuple[Credentials, AsyncRedisLock]: """ ⚠️ WARNING: this locks credentials system-wide and blocks both acquiring and updating them elsewhere until the lock is released. @@ -94,23 +100,25 @@ class IntegrationCredentialsManager: """ # Use a low-priority (!time_sensitive) locking queue on top of the general lock # to allow priority access for refreshing/updating the tokens. - with self._locked(user_id, credentials_id, "!time_sensitive"): - lock = self._acquire_lock(user_id, credentials_id) - credentials = self.get(user_id, credentials_id, lock=False) + async with self._locked(user_id, credentials_id, "!time_sensitive"): + lock = await self._acquire_lock(user_id, credentials_id) + credentials = await self.get(user_id, credentials_id, lock=False) if not credentials: raise ValueError( f"Credentials #{credentials_id} for user #{user_id} not found" ) return credentials, lock - def cached_getter(self, user_id: str) -> Callable[[str], "Credentials | None"]: + def cached_getter( + self, user_id: str + ) -> Callable[[str], "Coroutine[Any, Any, Credentials | None]"]: all_credentials = None - def get_credentials(creds_id: str) -> "Credentials | None": + async def get_credentials(creds_id: str) -> "Credentials | None": nonlocal all_credentials if not all_credentials: # Fetch credentials on first necessity - all_credentials = self.store.get_all_creds(user_id) + all_credentials = await self.store.get_all_creds(user_id) credential = next((c for c in all_credentials if c.id == creds_id), None) if not credential: @@ -120,15 +128,15 @@ class IntegrationCredentialsManager: return credential # Credential is OAuth2 credential and has expiration timestamp - return self.refresh_if_needed(user_id, credential) + return await self.refresh_if_needed(user_id, credential) return get_credentials - def refresh_if_needed( + async def refresh_if_needed( self, user_id: str, credentials: OAuth2Credentials, lock: bool = True ) -> OAuth2Credentials: - with self._locked(user_id, credentials.id, "refresh"): - oauth_handler = _get_provider_oauth_handler(credentials.provider) + async with self._locked(user_id, credentials.id, "refresh"): + oauth_handler = await _get_provider_oauth_handler(credentials.provider) if oauth_handler.needs_refresh(credentials): logger.debug( f"Refreshing '{credentials.provider}' " @@ -137,50 +145,53 @@ class IntegrationCredentialsManager: _lock = None if lock: # Wait until the credentials are no longer in use anywhere - _lock = self._acquire_lock(user_id, credentials.id) + _lock = await self._acquire_lock(user_id, credentials.id) - fresh_credentials = oauth_handler.refresh_tokens(credentials) - self.store.update_creds(user_id, fresh_credentials) - if _lock and _lock.locked() and _lock.owned(): - _lock.release() + fresh_credentials = await oauth_handler.refresh_tokens(credentials) + await self.store.update_creds(user_id, fresh_credentials) + if _lock and (await _lock.locked()) and (await _lock.owned()): + await _lock.release() credentials = fresh_credentials return credentials - def update(self, user_id: str, updated: Credentials) -> None: - with self._locked(user_id, updated.id): - self.store.update_creds(user_id, updated) + async def update(self, user_id: str, updated: Credentials) -> None: + async with self._locked(user_id, updated.id): + await self.store.update_creds(user_id, updated) - def delete(self, user_id: str, credentials_id: str) -> None: - with self._locked(user_id, credentials_id): - self.store.delete_creds_by_id(user_id, credentials_id) + async def delete(self, user_id: str, credentials_id: str) -> None: + async with self._locked(user_id, credentials_id): + await self.store.delete_creds_by_id(user_id, credentials_id) # -- Locking utilities -- # - def _acquire_lock(self, user_id: str, credentials_id: str, *args: str) -> RedisLock: + async def _acquire_lock( + self, user_id: str, credentials_id: str, *args: str + ) -> AsyncRedisLock: key = ( f"user:{user_id}", f"credentials:{credentials_id}", *args, ) - return self._locks.acquire(key) + locks = await self.locks() + return await locks.acquire(key) - @contextmanager - def _locked(self, user_id: str, credentials_id: str, *args: str): - lock = self._acquire_lock(user_id, credentials_id, *args) + @asynccontextmanager + async def _locked(self, user_id: str, credentials_id: str, *args: str): + lock = await self._acquire_lock(user_id, credentials_id, *args) try: yield finally: - if lock.locked() and lock.owned(): - lock.release() + if (await lock.locked()) and (await lock.owned()): + await lock.release() - def release_all_locks(self): + async def release_all_locks(self): """Call this on process termination to ensure all locks are released""" - self._locks.release_all_locks() - self.store.locks.release_all_locks() + await (await self.locks()).release_all_locks() + await (await self.store.locks()).release_all_locks() -def _get_provider_oauth_handler(provider_name_str: str) -> "BaseOAuthHandler": +async def _get_provider_oauth_handler(provider_name_str: str) -> "BaseOAuthHandler": provider_name = ProviderName(provider_name_str) if provider_name not in HANDLERS_BY_NAME: raise KeyError(f"Unknown provider '{provider_name}'") diff --git a/autogpt_platform/backend/backend/integrations/oauth/base.py b/autogpt_platform/backend/backend/integrations/oauth/base.py index fc6c68c161..b8d08582b2 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/base.py +++ b/autogpt_platform/backend/backend/integrations/oauth/base.py @@ -32,7 +32,7 @@ class BaseOAuthHandler(ABC): @abstractmethod # --8<-- [start:BaseOAuthHandler4] - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: # --8<-- [end:BaseOAuthHandler4] @@ -41,31 +41,33 @@ class BaseOAuthHandler(ABC): @abstractmethod # --8<-- [start:BaseOAuthHandler5] - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: # --8<-- [end:BaseOAuthHandler5] """Implements the token refresh mechanism""" ... @abstractmethod # --8<-- [start:BaseOAuthHandler6] - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: # --8<-- [end:BaseOAuthHandler6] """Revokes the given token at provider, returns False provider does not support it""" ... - def refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: if credentials.provider != self.PROVIDER_NAME: raise ValueError( f"{self.__class__.__name__} can not refresh tokens " f"for other provider '{credentials.provider}'" ) - return self._refresh_tokens(credentials) + return await self._refresh_tokens(credentials) - def get_access_token(self, credentials: OAuth2Credentials) -> str: + async def get_access_token(self, credentials: OAuth2Credentials) -> str: """Returns a valid access token, refreshing it first if needed""" if self.needs_refresh(credentials): - credentials = self.refresh_tokens(credentials) + credentials = await self.refresh_tokens(credentials) return credentials.access_token.get_secret_value() def needs_refresh(self, credentials: OAuth2Credentials) -> bool: diff --git a/autogpt_platform/backend/backend/integrations/oauth/github.py b/autogpt_platform/backend/backend/integrations/oauth/github.py index e6b3db37b4..ebec116660 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/github.py +++ b/autogpt_platform/backend/backend/integrations/oauth/github.py @@ -4,7 +4,7 @@ from urllib.parse import urlencode from backend.data.model import OAuth2Credentials from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests from .base import BaseOAuthHandler @@ -45,12 +45,14 @@ class GitHubOAuthHandler(BaseOAuthHandler): } return f"{self.auth_base_url}?{urlencode(params)}" - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: - return self._request_tokens({"code": code, "redirect_uri": self.redirect_uri}) + return await self._request_tokens( + {"code": code, "redirect_uri": self.redirect_uri} + ) - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: if not credentials.access_token: raise ValueError("No access token to revoke") @@ -59,7 +61,7 @@ class GitHubOAuthHandler(BaseOAuthHandler): "X-GitHub-Api-Version": "2022-11-28", } - requests.delete( + await Requests().delete( url=self.revoke_url.format(client_id=self.client_id), auth=(self.client_id, self.client_secret), headers=headers, @@ -67,18 +69,20 @@ class GitHubOAuthHandler(BaseOAuthHandler): ) return True - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: if not credentials.refresh_token: return credentials - return self._request_tokens( + return await self._request_tokens( { "refresh_token": credentials.refresh_token.get_secret_value(), "grant_type": "refresh_token", } ) - def _request_tokens( + async def _request_tokens( self, params: dict[str, str], current_credentials: Optional[OAuth2Credentials] = None, @@ -89,10 +93,12 @@ class GitHubOAuthHandler(BaseOAuthHandler): **params, } headers = {"Accept": "application/json"} - response = requests.post(self.token_url, data=request_body, headers=headers) + response = await Requests().post( + self.token_url, data=request_body, headers=headers + ) token_data: dict = response.json() - username = self._request_username(token_data["access_token"]) + username = await self._request_username(token_data["access_token"]) now = int(time.time()) new_credentials = OAuth2Credentials( @@ -124,7 +130,7 @@ class GitHubOAuthHandler(BaseOAuthHandler): new_credentials.id = current_credentials.id return new_credentials - def _request_username(self, access_token: str) -> str | None: + async def _request_username(self, access_token: str) -> str | None: url = "https://api.github.com/user" headers = { "Accept": "application/vnd.github+json", @@ -132,13 +138,14 @@ class GitHubOAuthHandler(BaseOAuthHandler): "X-GitHub-Api-Version": "2022-11-28", } - response = requests.get(url, headers=headers) + response = await Requests().get(url, headers=headers) if not response.ok: return None # Get the login (username) - return response.json().get("login") + resp = response.json() + return resp.get("login") # --8<-- [end:GithubOAuthHandlerExample] diff --git a/autogpt_platform/backend/backend/integrations/oauth/google.py b/autogpt_platform/backend/backend/integrations/oauth/google.py index 310eb5ae73..bba2bc71c5 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/google.py +++ b/autogpt_platform/backend/backend/integrations/oauth/google.py @@ -54,7 +54,7 @@ class GoogleOAuthHandler(BaseOAuthHandler): ) return authorization_url - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: logger.debug(f"Exchanging code for tokens with scopes: {scopes}") @@ -76,7 +76,7 @@ class GoogleOAuthHandler(BaseOAuthHandler): logger.debug(f"Scopes granted by Google: {granted_scopes}") google_creds = flow.credentials - logger.debug(f"Received credentials: {google_creds}") + logger.debug("Received credentials") logger.debug("Requesting user email") username = self._request_email(google_creds) @@ -106,7 +106,7 @@ class GoogleOAuthHandler(BaseOAuthHandler): return credentials - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: session = AuthorizedSession(credentials) session.post( self.revoke_uri, @@ -127,7 +127,9 @@ class GoogleOAuthHandler(BaseOAuthHandler): return None return response.json()["email"] - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: # Google credentials should ALWAYS have a refresh token assert credentials.refresh_token diff --git a/autogpt_platform/backend/backend/integrations/oauth/linear.py b/autogpt_platform/backend/backend/integrations/oauth/linear.py index fd9d379c1e..09e3c6d537 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/linear.py +++ b/autogpt_platform/backend/backend/integrations/oauth/linear.py @@ -7,7 +7,7 @@ from pydantic import SecretStr from backend.blocks.linear._api import LinearAPIException from backend.data.model import APIKeyCredentials, OAuth2Credentials from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests from .base import BaseOAuthHandler @@ -40,12 +40,14 @@ class LinearOAuthHandler(BaseOAuthHandler): } return f"{self.auth_base_url}?{urlencode(params)}" - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: - return self._request_tokens({"code": code, "redirect_uri": self.redirect_uri}) + return await self._request_tokens( + {"code": code, "redirect_uri": self.redirect_uri} + ) - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: if not credentials.access_token: raise ValueError("No access token to revoke") @@ -53,7 +55,7 @@ class LinearOAuthHandler(BaseOAuthHandler): "Authorization": f"Bearer {credentials.access_token.get_secret_value()}" } - response = requests.post(self.revoke_url, headers=headers) + response = await Requests().post(self.revoke_url, headers=headers) if not response.ok: try: error_data = response.json() @@ -61,26 +63,28 @@ class LinearOAuthHandler(BaseOAuthHandler): except json.JSONDecodeError: error_message = response.text raise LinearAPIException( - f"Failed to revoke Linear tokens ({response.status_code}): {error_message}", - response.status_code, + f"Failed to revoke Linear tokens ({response.status}): {error_message}", + response.status, ) return True # Linear doesn't return JSON on successful revoke - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: if not credentials.refresh_token: raise ValueError( "No refresh token available." ) # Linear uses non-expiring tokens - return self._request_tokens( + return await self._request_tokens( { "refresh_token": credentials.refresh_token.get_secret_value(), "grant_type": "refresh_token", } ) - def _request_tokens( + async def _request_tokens( self, params: dict[str, str], current_credentials: Optional[OAuth2Credentials] = None, @@ -95,18 +99,19 @@ class LinearOAuthHandler(BaseOAuthHandler): headers = { "Content-Type": "application/x-www-form-urlencoded" } # Correct header for token request - response = requests.post(self.token_url, data=request_body, headers=headers) + response = await Requests().post( + self.token_url, data=request_body, headers=headers + ) if not response.ok: try: error_data = response.json() error_message = error_data.get("error", "Unknown error") - except json.JSONDecodeError: error_message = response.text raise LinearAPIException( - f"Failed to fetch Linear tokens ({response.status_code}): {error_message}", - response.status_code, + f"Failed to fetch Linear tokens ({response.status}): {error_message}", + response.status, ) token_data = response.json() @@ -132,13 +137,11 @@ class LinearOAuthHandler(BaseOAuthHandler): new_credentials.id = current_credentials.id return new_credentials - def _request_username(self, access_token: str) -> Optional[str]: - + async def _request_username(self, access_token: str) -> Optional[str]: # Use the LinearClient to fetch user details using GraphQL from backend.blocks.linear._api import LinearClient try: - linear_client = LinearClient( APIKeyCredentials( api_key=SecretStr(access_token), @@ -156,10 +159,9 @@ class LinearOAuthHandler(BaseOAuthHandler): } """ - response = linear_client.query(query) + response = await linear_client.query(query) return response["viewer"]["name"] except Exception as e: # Handle any errors - print(f"Error fetching username: {e}") return None diff --git a/autogpt_platform/backend/backend/integrations/oauth/notion.py b/autogpt_platform/backend/backend/integrations/oauth/notion.py index 3cd3249fef..67fc5bcc25 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/notion.py +++ b/autogpt_platform/backend/backend/integrations/oauth/notion.py @@ -4,7 +4,7 @@ from urllib.parse import urlencode from backend.data.model import OAuth2Credentials from backend.integrations.providers import ProviderName -from backend.util.request import requests +from backend.util.request import Requests from .base import BaseOAuthHandler @@ -39,7 +39,7 @@ class NotionOAuthHandler(BaseOAuthHandler): } return f"{self.auth_base_url}?{urlencode(params)}" - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: request_body = { @@ -52,7 +52,9 @@ class NotionOAuthHandler(BaseOAuthHandler): "Authorization": f"Basic {auth_str}", "Accept": "application/json", } - response = requests.post(self.token_url, json=request_body, headers=headers) + response = await Requests().post( + self.token_url, json=request_body, headers=headers + ) token_data = response.json() # Email is only available for non-bot users email = ( @@ -80,11 +82,13 @@ class NotionOAuthHandler(BaseOAuthHandler): }, ) - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: # Notion doesn't support token revocation return False - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: # Notion doesn't support token refresh return credentials diff --git a/autogpt_platform/backend/backend/integrations/oauth/todoist.py b/autogpt_platform/backend/backend/integrations/oauth/todoist.py index 543a7de84b..66d34f95e8 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/todoist.py +++ b/autogpt_platform/backend/backend/integrations/oauth/todoist.py @@ -1,10 +1,9 @@ import urllib.parse from typing import ClassVar, Optional -import requests - from backend.data.model import OAuth2Credentials, ProviderName from backend.integrations.oauth.base import BaseOAuthHandler +from backend.util.request import Requests class TodoistOAuthHandler(BaseOAuthHandler): @@ -36,7 +35,7 @@ class TodoistOAuthHandler(BaseOAuthHandler): return f"{self.AUTHORIZE_URL}?{urllib.parse.urlencode(params)}" - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: """Exchange authorization code for access tokens""" @@ -48,17 +47,14 @@ class TodoistOAuthHandler(BaseOAuthHandler): "redirect_uri": self.redirect_uri, } - response = requests.post(self.TOKEN_URL, data=data) - response.raise_for_status() - + response = await Requests().post(self.TOKEN_URL, data=data) tokens = response.json() - response = requests.post( + response = await Requests().post( "https://api.todoist.com/sync/v9/sync", headers={"Authorization": f"Bearer {tokens['access_token']}"}, data={"sync_token": "*", "resource_types": '["user"]'}, ) - response.raise_for_status() user_info = response.json() user_email = user_info["user"].get("email") @@ -73,9 +69,11 @@ class TodoistOAuthHandler(BaseOAuthHandler): scopes=scopes, ) - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: # Todoist does not support token refresh return credentials - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: return False diff --git a/autogpt_platform/backend/backend/integrations/oauth/twitter.py b/autogpt_platform/backend/backend/integrations/oauth/twitter.py index 519ccd354e..486c14a210 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/twitter.py +++ b/autogpt_platform/backend/backend/integrations/oauth/twitter.py @@ -2,10 +2,9 @@ import time import urllib.parse from typing import ClassVar, Optional -import requests - from backend.data.model import OAuth2Credentials, ProviderName from backend.integrations.oauth.base import BaseOAuthHandler +from backend.util.request import Requests class TwitterOAuthHandler(BaseOAuthHandler): @@ -62,7 +61,7 @@ class TwitterOAuthHandler(BaseOAuthHandler): return f"{self.AUTHORIZE_URL}?{urllib.parse.urlencode(params)}" - def exchange_code_for_tokens( + async def exchange_code_for_tokens( self, code: str, scopes: list[str], code_verifier: Optional[str] ) -> OAuth2Credentials: """Exchange authorization code for access tokens""" @@ -78,12 +77,12 @@ class TwitterOAuthHandler(BaseOAuthHandler): auth = (self.client_id, self.client_secret) - response = requests.post(self.TOKEN_URL, headers=headers, data=data, auth=auth) - response.raise_for_status() - + response = await Requests().post( + self.TOKEN_URL, headers=headers, data=data, auth=auth + ) tokens = response.json() - username = self._get_username(tokens["access_token"]) + username = await self._get_username(tokens["access_token"]) return OAuth2Credentials( provider=self.PROVIDER_NAME, @@ -96,20 +95,21 @@ class TwitterOAuthHandler(BaseOAuthHandler): scopes=scopes, ) - def _get_username(self, access_token: str) -> str: + async def _get_username(self, access_token: str) -> str: """Get the username from the access token""" headers = {"Authorization": f"Bearer {access_token}"} params = {"user.fields": "username"} - response = requests.get( + response = await Requests().get( f"{self.USERNAME_URL}?{urllib.parse.urlencode(params)}", headers=headers ) - response.raise_for_status() return response.json()["data"]["username"] - def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials: + async def _refresh_tokens( + self, credentials: OAuth2Credentials + ) -> OAuth2Credentials: """Refresh access tokens using refresh token""" if not credentials.refresh_token: raise ValueError("No refresh token available") @@ -122,18 +122,19 @@ class TwitterOAuthHandler(BaseOAuthHandler): auth = (self.client_id, self.client_secret) - response = requests.post(self.TOKEN_URL, headers=header, data=data, auth=auth) + response = await Requests().post( + self.TOKEN_URL, headers=header, data=data, auth=auth + ) - try: - response.raise_for_status() - except requests.exceptions.HTTPError as e: - print("HTTP Error:", e) - print("Response Content:", response.text) - raise + if not response.ok: + error_text = response.text + print("HTTP Error:", response.status) + print("Response Content:", error_text) + raise ValueError(f"HTTP Error: {response.status} - {error_text}") tokens = response.json() - username = self._get_username(tokens["access_token"]) + username = await self._get_username(tokens["access_token"]) return OAuth2Credentials( id=credentials.id, @@ -147,7 +148,7 @@ class TwitterOAuthHandler(BaseOAuthHandler): refresh_token_expires_at=None, ) - def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: + async def revoke_tokens(self, credentials: OAuth2Credentials) -> bool: """Revoke the access token""" header = {"Content-Type": "application/x-www-form-urlencoded"} @@ -159,13 +160,14 @@ class TwitterOAuthHandler(BaseOAuthHandler): auth = (self.client_id, self.client_secret) - response = requests.post(self.REVOKE_URL, headers=header, data=data, auth=auth) + response = await Requests().post( + self.REVOKE_URL, headers=header, data=data, auth=auth + ) - try: - response.raise_for_status() - except requests.exceptions.HTTPError as e: - print("HTTP Error:", e) - print("Response Content:", response.text) - raise + if not response.ok: + error_text = response.text + print("HTTP Error:", response.status) + print("Response Content:", error_text) + raise ValueError(f"HTTP Error: {response.status} - {error_text}") - return response.status_code == 200 + return response.ok diff --git a/autogpt_platform/backend/backend/integrations/webhooks/github.py b/autogpt_platform/backend/backend/integrations/webhooks/github.py index 6a39192045..5d2977cacc 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/github.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/github.py @@ -2,13 +2,13 @@ import hashlib import hmac import logging -import requests from fastapi import HTTPException, Request from strenum import StrEnum from backend.data import integrations from backend.data.model import Credentials from backend.integrations.providers import ProviderName +from backend.util.request import Requests, Response from ._base import BaseWebhooksManager @@ -73,9 +73,9 @@ class GithubWebhooksManager(BaseWebhooksManager): repo, github_hook_id = webhook.resource, webhook.provider_webhook_id ping_url = f"{self.GITHUB_API_URL}/repos/{repo}/hooks/{github_hook_id}/pings" - response = requests.post(ping_url, headers=headers) + response = await Requests().post(ping_url, headers=headers) - if response.status_code != 204: + if response.status != 204: error_msg = extract_github_error_msg(response) raise ValueError(f"Failed to ping GitHub webhook: {error_msg}") @@ -110,13 +110,13 @@ class GithubWebhooksManager(BaseWebhooksManager): }, } - response = requests.post( + response = await Requests().post( f"{self.GITHUB_API_URL}/repos/{resource}/hooks", headers=headers, json=webhook_data, ) - if response.status_code != 201: + if response.status != 201: error_msg = extract_github_error_msg(response) if "not found" in error_msg.lower(): error_msg = ( @@ -126,8 +126,9 @@ class GithubWebhooksManager(BaseWebhooksManager): ) raise ValueError(f"Failed to create GitHub webhook: {error_msg}") - webhook_id = response.json()["id"] - config = response.json()["config"] + resp = response.json() + webhook_id = resp["id"] + config = resp["config"] return str(webhook_id), config @@ -153,9 +154,9 @@ class GithubWebhooksManager(BaseWebhooksManager): f"Unsupported webhook type '{webhook.webhook_type}'" ) - response = requests.delete(delete_url, headers=headers) + response = await Requests().delete(delete_url, headers=headers) - if response.status_code not in [204, 404]: + if response.status not in [204, 404]: # 204 means successful deletion, 404 means the webhook was already deleted error_msg = extract_github_error_msg(response) raise ValueError(f"Failed to delete GitHub webhook: {error_msg}") @@ -166,7 +167,7 @@ class GithubWebhooksManager(BaseWebhooksManager): # --8<-- [end:GithubWebhooksManager] -def extract_github_error_msg(response: requests.Response) -> str: +def extract_github_error_msg(response: Response) -> str: error_msgs = [] resp = response.json() if resp.get("message"): diff --git a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py index 898c6772bb..01e7b3a49e 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py @@ -37,7 +37,7 @@ async def on_graph_activate(graph: "GraphModel", user_id: str): ) ) and (creds_meta := new_node.input_default.get(creds_field_name)) - and not (node_credentials := get_credentials(creds_meta["id"])) + and not (node_credentials := await get_credentials(creds_meta["id"])) ): raise ValueError( f"Node #{new_node.id} input '{creds_field_name}' updated with " @@ -74,7 +74,7 @@ async def on_graph_deactivate(graph: "GraphModel", user_id: str): ) ) and (creds_meta := node.input_default.get(creds_field_name)) - and not (node_credentials := get_credentials(creds_meta["id"])) + and not (node_credentials := await get_credentials(creds_meta["id"])) ): logger.error( f"Node #{node.id} input '{creds_field_name}' referenced non-existent " diff --git a/autogpt_platform/backend/backend/integrations/webhooks/slant3d.py b/autogpt_platform/backend/backend/integrations/webhooks/slant3d.py index 189ab72083..bc0337d4c5 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/slant3d.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/slant3d.py @@ -1,12 +1,12 @@ import logging -import requests from fastapi import Request from backend.data import integrations from backend.data.model import APIKeyCredentials, Credentials from backend.integrations.providers import ProviderName from backend.integrations.webhooks._base import BaseWebhooksManager +from backend.util.request import Requests logger = logging.getLogger(__name__) @@ -39,7 +39,7 @@ class Slant3DWebhooksManager(BaseWebhooksManager): # Slant3D's API doesn't use events list, just register for all order updates payload = {"endPoint": ingress_url} - response = requests.post( + response = await Requests().post( f"{self.BASE_URL}/customer/webhookSubscribe", headers=headers, json=payload ) diff --git a/autogpt_platform/backend/backend/notifications/notifications.py b/autogpt_platform/backend/backend/notifications/notifications.py index 808a8cb699..dbbd8fe075 100644 --- a/autogpt_platform/backend/backend/notifications/notifications.py +++ b/autogpt_platform/backend/backend/notifications/notifications.py @@ -1,6 +1,6 @@ +import asyncio import logging -import time -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ProcessPoolExecutor from datetime import datetime, timedelta, timezone from typing import Callable @@ -39,9 +39,11 @@ from backend.data.user import generate_unsubscribe_link from backend.notifications.email import EmailSender from backend.util.logging import TruncatedLogger from backend.util.metrics import discord_send_alert +from backend.util.retry import continuous_retry from backend.util.service import ( AppService, AppServiceClient, + endpoint_to_sync, expose, get_service_client, ) @@ -55,7 +57,7 @@ NOTIFICATION_EXCHANGE = Exchange(name="notifications", type=ExchangeType.TOPIC) DEAD_LETTER_EXCHANGE = Exchange(name="dead_letter", type=ExchangeType.TOPIC) EXCHANGES = [NOTIFICATION_EXCHANGE, DEAD_LETTER_EXCHANGE] -background_executor = ThreadPoolExecutor(max_workers=2) +background_executor = ProcessPoolExecutor(max_workers=2) def create_notification_config() -> RabbitMQConfig: @@ -231,9 +233,9 @@ class NotificationManager(AppService): @expose def queue_weekly_summary(self): - background_executor.submit(self._queue_weekly_summary) + background_executor.submit(lambda: asyncio.run(self._queue_weekly_summary())) - def _queue_weekly_summary(self): + async def _queue_weekly_summary(self): """Process weekly summary for specified notification types""" try: logger.info("Processing weekly summary queuing operation") @@ -245,8 +247,7 @@ class NotificationManager(AppService): start_time=start_time.isoformat(), ) for user in users: - - self._queue_scheduled_notification( + await self._queue_scheduled_notification( SummaryParamsEventModel( user_id=user, type=NotificationType.WEEKLY_SUMMARY, @@ -387,10 +388,10 @@ class NotificationManager(AppService): } @expose - def discord_system_alert(self, content: str): - discord_send_alert(content) + async def discord_system_alert(self, content: str): + await discord_send_alert(content) - def _queue_scheduled_notification(self, event: SummaryParamsEventModel): + async def _queue_scheduled_notification(self, event: SummaryParamsEventModel): """Queue a scheduled notification - exposed method for other services to call""" try: logger.debug(f"Received Request to queue scheduled notification {event=}") @@ -399,12 +400,10 @@ class NotificationManager(AppService): routing_key = get_routing_key(event.type) # Publish to RabbitMQ - self.run_and_wait( - self.rabbit.publish_message( - routing_key=routing_key, - message=event.model_dump_json(), - exchange=next(ex for ex in EXCHANGES if ex.name == exchange), - ) + await self.rabbit.publish_message( + routing_key=routing_key, + message=event.model_dump_json(), + exchange=next(ex for ex in EXCHANGES if ex.name == exchange), ) except Exception as e: @@ -695,7 +694,7 @@ class NotificationManager(AppService): logger.exception(f"Error processing notification for summary queue: {e}") return False - def _run_queue( + async def _run_queue( self, queue: aio_pika.abc.AbstractQueue, process_func: Callable[[str], bool], @@ -704,12 +703,12 @@ class NotificationManager(AppService): message: aio_pika.abc.AbstractMessage | None = None try: # This parameter "no_ack" is named like shit, think of it as "auto_ack" - message = self.run_and_wait(queue.get(timeout=1.0, no_ack=False)) + message = await queue.get(timeout=1.0, no_ack=False) result = process_func(message.body.decode()) if result: - self.run_and_wait(message.ack()) + await message.ack() else: - self.run_and_wait(message.reject(requeue=False)) + await message.reject(requeue=False) except QueueEmpty: logger.debug(f"Queue {error_queue_name} empty") @@ -720,61 +719,58 @@ class NotificationManager(AppService): logger.error( f"Error in notification service loop, message rejected {e}" ) - self.run_and_wait(message.reject(requeue=False)) + await message.reject(requeue=False) else: logger.exception( f"Error in notification service loop, message unable to be rejected, and will have to be manually removed to free space in the queue: {e=}" ) + @continuous_retry() def run_service(self): + self.run_and_wait(self._run_service()) + + async def _run_service(self): logger.info(f"[{self.service_name}] ⏳ Configuring RabbitMQ...") self.rabbitmq_service = rabbitmq.AsyncRabbitMQ(self.rabbitmq_config) - self.run_and_wait(self.rabbitmq_service.connect()) + await self.rabbitmq_service.connect() logger.info(f"[{self.service_name}] Started notification service") # Set up queue consumers - channel = self.run_and_wait(self.rabbit.get_channel()) + channel = await self.rabbit.get_channel() - immediate_queue = self.run_and_wait( - channel.get_queue("immediate_notifications") - ) - batch_queue = self.run_and_wait(channel.get_queue("batch_notifications")) + immediate_queue = await channel.get_queue("immediate_notifications") + batch_queue = await channel.get_queue("batch_notifications") - admin_queue = self.run_and_wait(channel.get_queue("admin_notifications")) + admin_queue = await channel.get_queue("admin_notifications") - summary_queue = self.run_and_wait(channel.get_queue("summary_notifications")) + summary_queue = await channel.get_queue("summary_notifications") while self.running: try: - self._run_queue( + await self._run_queue( queue=immediate_queue, process_func=self._process_immediate, error_queue_name="immediate_notifications", ) - self._run_queue( + await self._run_queue( queue=admin_queue, process_func=self._process_admin_message, error_queue_name="admin_notifications", ) - self._run_queue( + await self._run_queue( queue=batch_queue, process_func=self._process_batch, error_queue_name="batch_notifications", ) - - self._run_queue( + await self._run_queue( queue=summary_queue, process_func=self._process_summary, error_queue_name="summary_notifications", ) - - time.sleep(0.1) - + await asyncio.sleep(0.1) except QueueEmpty as e: logger.debug(f"Queue empty: {e}") - except Exception as e: - logger.error(f"Error in notification service loop: {e}") def cleanup(self): """Cleanup service resources""" @@ -791,4 +787,4 @@ class NotificationManagerClient(AppServiceClient): process_existing_batches = NotificationManager.process_existing_batches queue_weekly_summary = NotificationManager.queue_weekly_summary - discord_system_alert = NotificationManager.discord_system_alert + discord_system_alert = endpoint_to_sync(NotificationManager.discord_system_alert) diff --git a/autogpt_platform/backend/backend/server/external/api.py b/autogpt_platform/backend/backend/server/external/api.py index 3236766fdd..8ed15c1385 100644 --- a/autogpt_platform/backend/backend/server/external/api.py +++ b/autogpt_platform/backend/backend/server/external/api.py @@ -1,5 +1,7 @@ from fastapi import FastAPI +from backend.server.middleware.security import SecurityHeadersMiddleware + from .routes.v1 import v1_router external_app = FastAPI( @@ -8,4 +10,6 @@ external_app = FastAPI( docs_url="/docs", version="1.0", ) + +external_app.add_middleware(SecurityHeadersMiddleware) external_app.include_router(v1_router, prefix="/v1") diff --git a/autogpt_platform/backend/backend/server/external/routes/v1.py b/autogpt_platform/backend/backend/server/external/routes/v1.py index 15fe8dc7ee..baf77c0e75 100644 --- a/autogpt_platform/backend/backend/server/external/routes/v1.py +++ b/autogpt_platform/backend/backend/server/external/routes/v1.py @@ -12,7 +12,7 @@ from backend.data import graph as graph_db from backend.data.api_key import APIKey from backend.data.block import BlockInput, CompletedBlockOutput from backend.data.execution import NodeExecutionResult -from backend.executor.utils import add_graph_execution_async +from backend.executor.utils import add_graph_execution from backend.server.external.middleware import require_permission from backend.util.settings import Settings @@ -71,7 +71,7 @@ def get_graph_blocks() -> Sequence[dict[Any, Any]]: tags=["blocks"], dependencies=[Depends(require_permission(APIKeyPermission.EXECUTE_BLOCK))], ) -def execute_graph_block( +async def execute_graph_block( block_id: str, data: BlockInput, api_key: APIKey = Depends(require_permission(APIKeyPermission.EXECUTE_BLOCK)), @@ -81,7 +81,7 @@ def execute_graph_block( raise HTTPException(status_code=404, detail=f"Block #{block_id} not found.") output = defaultdict(list) - for name, data in obj.execute(data): + async for name, data in obj.execute(data): output[name].append(data) return output @@ -97,7 +97,7 @@ async def execute_graph( api_key: APIKey = Depends(require_permission(APIKeyPermission.EXECUTE_GRAPH)), ) -> dict[str, Any]: try: - graph_exec = await add_graph_execution_async( + graph_exec = await add_graph_execution( graph_id=graph_id, user_id=api_key.user_id, inputs=node_input, diff --git a/autogpt_platform/backend/backend/server/integrations/router.py b/autogpt_platform/backend/backend/server/integrations/router.py index 4739bd8417..26f33eba37 100644 --- a/autogpt_platform/backend/backend/server/integrations/router.py +++ b/autogpt_platform/backend/backend/server/integrations/router.py @@ -15,7 +15,7 @@ from backend.data.integrations import ( wait_for_webhook_event, ) from backend.data.model import Credentials, CredentialsType, OAuth2Credentials -from backend.executor.utils import add_graph_execution_async +from backend.executor.utils import add_graph_execution from backend.integrations.creds_manager import IntegrationCredentialsManager from backend.integrations.oauth import HANDLERS_BY_NAME from backend.integrations.providers import ProviderName @@ -41,7 +41,7 @@ class LoginResponse(BaseModel): @router.get("/{provider}/login") -def login( +async def login( provider: Annotated[ ProviderName, Path(title="The provider to initiate an OAuth flow for") ], @@ -56,7 +56,7 @@ def login( requested_scopes = scopes.split(",") if scopes else [] # Generate and store a secure random state token along with the scopes - state_token, code_challenge = creds_manager.store.store_state_token( + state_token, code_challenge = await creds_manager.store.store_state_token( user_id, provider, requested_scopes ) login_url = handler.get_login_url( @@ -76,7 +76,7 @@ class CredentialsMetaResponse(BaseModel): @router.post("/{provider}/callback") -def callback( +async def callback( provider: Annotated[ ProviderName, Path(title="The target provider for this OAuth exchange") ], @@ -89,7 +89,9 @@ def callback( handler = _get_provider_oauth_handler(request, provider) # Verify the state token - valid_state = creds_manager.store.verify_state_token(user_id, state_token, provider) + valid_state = await creds_manager.store.verify_state_token( + user_id, state_token, provider + ) if not valid_state: logger.warning(f"Invalid or expired state token for user {user_id}") @@ -100,7 +102,7 @@ def callback( scopes = handler.handle_default_scopes(scopes) - credentials = handler.exchange_code_for_tokens( + credentials = await handler.exchange_code_for_tokens( code, scopes, valid_state.code_verifier ) @@ -134,7 +136,7 @@ def callback( ) # TODO: Allow specifying `title` to set on `credentials` - creds_manager.create(user_id, credentials) + await creds_manager.create(user_id, credentials) logger.debug( f"Successfully processed OAuth callback for user {user_id} " @@ -151,10 +153,10 @@ def callback( @router.get("/credentials") -def list_credentials( +async def list_credentials( user_id: Annotated[str, Depends(get_user_id)], ) -> list[CredentialsMetaResponse]: - credentials = creds_manager.store.get_all_creds(user_id) + credentials = await creds_manager.store.get_all_creds(user_id) return [ CredentialsMetaResponse( id=cred.id, @@ -169,13 +171,13 @@ def list_credentials( @router.get("/{provider}/credentials") -def list_credentials_by_provider( +async def list_credentials_by_provider( provider: Annotated[ ProviderName, Path(title="The provider to list credentials for") ], user_id: Annotated[str, Depends(get_user_id)], ) -> list[CredentialsMetaResponse]: - credentials = creds_manager.store.get_creds_by_provider(user_id, provider) + credentials = await creds_manager.store.get_creds_by_provider(user_id, provider) return [ CredentialsMetaResponse( id=cred.id, @@ -190,14 +192,14 @@ def list_credentials_by_provider( @router.get("/{provider}/credentials/{cred_id}") -def get_credential( +async def get_credential( provider: Annotated[ ProviderName, Path(title="The provider to retrieve credentials for") ], cred_id: Annotated[str, Path(title="The ID of the credentials to retrieve")], user_id: Annotated[str, Depends(get_user_id)], ) -> Credentials: - credential = creds_manager.get(user_id, cred_id) + credential = await creds_manager.get(user_id, cred_id) if not credential: raise HTTPException(status_code=404, detail="Credentials not found") if credential.provider != provider: @@ -208,7 +210,7 @@ def get_credential( @router.post("/{provider}/credentials", status_code=201) -def create_credentials( +async def create_credentials( user_id: Annotated[str, Depends(get_user_id)], provider: Annotated[ ProviderName, Path(title="The provider to create credentials for") @@ -217,7 +219,7 @@ def create_credentials( ) -> Credentials: credentials.provider = provider try: - creds_manager.create(user_id, credentials) + await creds_manager.create(user_id, credentials) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to store credentials: {str(e)}" @@ -252,7 +254,7 @@ async def delete_credentials( bool, Query(title="Whether to proceed if any linked webhooks are still in use") ] = False, ) -> CredentialsDeletionResponse | CredentialsDeletionNeedsConfirmationResponse: - creds = creds_manager.store.get_creds_by_id(user_id, cred_id) + creds = await creds_manager.store.get_creds_by_id(user_id, cred_id) if not creds: raise HTTPException(status_code=404, detail="Credentials not found") if creds.provider != provider: @@ -265,12 +267,12 @@ async def delete_credentials( except NeedConfirmation as e: return CredentialsDeletionNeedsConfirmationResponse(message=str(e)) - creds_manager.delete(user_id, cred_id) + await creds_manager.delete(user_id, cred_id) tokens_revoked = None if isinstance(creds, OAuth2Credentials): handler = _get_provider_oauth_handler(request, provider) - tokens_revoked = handler.revoke_tokens(creds) + tokens_revoked = await handler.revoke_tokens(creds) return CredentialsDeletionResponse(revoked=tokens_revoked) @@ -329,7 +331,7 @@ async def webhook_ingress_generic( continue logger.debug(f"Executing graph #{node.graph_id} node #{node.id}") executions.append( - add_graph_execution_async( + add_graph_execution( user_id=webhook.user_id, graph_id=node.graph_id, graph_version=node.graph_version, @@ -348,7 +350,7 @@ async def webhook_ping( webhook_manager = get_webhook_manager(webhook.provider) credentials = ( - creds_manager.get(user_id, webhook.credentials_id) + await creds_manager.get(user_id, webhook.credentials_id) if webhook.credentials_id else None ) diff --git a/autogpt_platform/backend/backend/server/middleware/security.py b/autogpt_platform/backend/backend/server/middleware/security.py new file mode 100644 index 0000000000..e6e23467ca --- /dev/null +++ b/autogpt_platform/backend/backend/server/middleware/security.py @@ -0,0 +1,93 @@ +import re +from typing import Set + +from fastapi import Request, Response +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.types import ASGIApp + + +class SecurityHeadersMiddleware(BaseHTTPMiddleware): + """ + Middleware to add security headers to responses, with cache control + disabled by default for all endpoints except those explicitly allowed. + """ + + CACHEABLE_PATHS: Set[str] = { + # Static assets + "/static", + "/_next/static", + "/assets", + "/images", + "/css", + "/js", + "/fonts", + # Public API endpoints + "/api/health", + "/api/v1/health", + "/api/status", + # Public store/marketplace pages (read-only) + "/api/store/agents", + "/api/v1/store/agents", + "/api/store/categories", + "/api/v1/store/categories", + "/api/store/featured", + "/api/v1/store/featured", + # Public graph templates (read-only, no user data) + "/api/graphs/templates", + "/api/v1/graphs/templates", + # Documentation endpoints + "/api/docs", + "/api/v1/docs", + "/docs", + "/swagger", + "/openapi.json", + # Favicon and manifest + "/favicon.ico", + "/manifest.json", + "/robots.txt", + "/sitemap.xml", + } + + def __init__(self, app: ASGIApp): + super().__init__(app) + # Compile regex patterns for wildcard matching + self.cacheable_patterns = [ + re.compile(pattern.replace("*", "[^/]+")) + for pattern in self.CACHEABLE_PATHS + if "*" in pattern + ] + self.exact_paths = {path for path in self.CACHEABLE_PATHS if "*" not in path} + + def is_cacheable_path(self, path: str) -> bool: + """Check if the given path is allowed to be cached.""" + # Check exact matches first + for cacheable_path in self.exact_paths: + if path.startswith(cacheable_path): + return True + + # Check pattern matches + for pattern in self.cacheable_patterns: + if pattern.match(path): + return True + + return False + + async def dispatch(self, request: Request, call_next): + response: Response = await call_next(request) + + # Add general security headers + response.headers["X-Content-Type-Options"] = "nosniff" + response.headers["X-Frame-Options"] = "DENY" + response.headers["X-XSS-Protection"] = "1; mode=block" + response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" + + # Default: Disable caching for all endpoints + # Only allow caching for explicitly permitted paths + if not self.is_cacheable_path(request.url.path): + response.headers["Cache-Control"] = ( + "no-store, no-cache, must-revalidate, private" + ) + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "0" + + return response diff --git a/autogpt_platform/backend/backend/server/middleware/security_test.py b/autogpt_platform/backend/backend/server/middleware/security_test.py new file mode 100644 index 0000000000..462e5b27ed --- /dev/null +++ b/autogpt_platform/backend/backend/server/middleware/security_test.py @@ -0,0 +1,143 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from starlette.applications import Starlette + +from backend.server.middleware.security import SecurityHeadersMiddleware + + +@pytest.fixture +def app(): + """Create a test FastAPI app with security middleware.""" + app = FastAPI() + app.add_middleware(SecurityHeadersMiddleware) + + @app.get("/api/auth/user") + def get_user(): + return {"user": "test"} + + @app.get("/api/v1/integrations/oauth/google") + def oauth_endpoint(): + return {"oauth": "data"} + + @app.get("/api/graphs/123/execute") + def execute_graph(): + return {"execution": "data"} + + @app.get("/api/integrations/credentials") + def get_credentials(): + return {"credentials": "sensitive"} + + @app.get("/api/store/agents") + def store_agents(): + return {"agents": "public list"} + + @app.get("/api/health") + def health_check(): + return {"status": "ok"} + + @app.get("/static/logo.png") + def static_file(): + return {"static": "content"} + + return app + + +@pytest.fixture +def client(app): + """Create a test client.""" + return TestClient(app) + + +def test_non_cacheable_endpoints_have_cache_control_headers(client): + """Test that non-cacheable endpoints (most endpoints) have proper cache control headers.""" + non_cacheable_endpoints = [ + "/api/auth/user", + "/api/v1/integrations/oauth/google", + "/api/graphs/123/execute", + "/api/integrations/credentials", + ] + + for endpoint in non_cacheable_endpoints: + response = client.get(endpoint) + + # Check cache control headers are present (default behavior) + assert ( + response.headers["Cache-Control"] + == "no-store, no-cache, must-revalidate, private" + ) + assert response.headers["Pragma"] == "no-cache" + assert response.headers["Expires"] == "0" + + # Check general security headers + assert response.headers["X-Content-Type-Options"] == "nosniff" + assert response.headers["X-Frame-Options"] == "DENY" + assert response.headers["X-XSS-Protection"] == "1; mode=block" + assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin" + + +def test_cacheable_endpoints_dont_have_cache_control_headers(client): + """Test that explicitly cacheable endpoints don't have restrictive cache control headers.""" + cacheable_endpoints = [ + "/api/store/agents", + "/api/health", + "/static/logo.png", + ] + + for endpoint in cacheable_endpoints: + response = client.get(endpoint) + + # Should NOT have restrictive cache control headers + assert ( + "Cache-Control" not in response.headers + or "no-store" not in response.headers.get("Cache-Control", "") + ) + assert ( + "Pragma" not in response.headers + or response.headers.get("Pragma") != "no-cache" + ) + assert ( + "Expires" not in response.headers or response.headers.get("Expires") != "0" + ) + + # Should still have general security headers + assert response.headers["X-Content-Type-Options"] == "nosniff" + assert response.headers["X-Frame-Options"] == "DENY" + assert response.headers["X-XSS-Protection"] == "1; mode=block" + assert response.headers["Referrer-Policy"] == "strict-origin-when-cross-origin" + + +def test_is_cacheable_path_detection(): + """Test the path detection logic.""" + middleware = SecurityHeadersMiddleware(Starlette()) + + # Test cacheable paths (allow list) + assert middleware.is_cacheable_path("/api/health") + assert middleware.is_cacheable_path("/api/v1/health") + assert middleware.is_cacheable_path("/static/image.png") + assert middleware.is_cacheable_path("/api/store/agents") + assert middleware.is_cacheable_path("/docs") + assert middleware.is_cacheable_path("/favicon.ico") + + # Test non-cacheable paths (everything else) + assert not middleware.is_cacheable_path("/api/auth/user") + assert not middleware.is_cacheable_path("/api/v1/integrations/oauth/callback") + assert not middleware.is_cacheable_path("/api/integrations/credentials/123") + assert not middleware.is_cacheable_path("/api/graphs/abc123/execute") + assert not middleware.is_cacheable_path("/api/store/xyz/submissions") + + +def test_path_prefix_matching(): + """Test that path prefix matching works correctly.""" + middleware = SecurityHeadersMiddleware(Starlette()) + + # Test that paths starting with cacheable prefixes are cacheable + assert middleware.is_cacheable_path("/static/css/style.css") + assert middleware.is_cacheable_path("/static/js/app.js") + assert middleware.is_cacheable_path("/assets/images/logo.png") + assert middleware.is_cacheable_path("/_next/static/chunks/main.js") + + # Test that other API paths are not cacheable by default + assert not middleware.is_cacheable_path("/api/users/profile") + assert not middleware.is_cacheable_path("/api/v1/private/data") + assert not middleware.is_cacheable_path("/api/billing/subscription") diff --git a/autogpt_platform/backend/backend/server/rest_api.py b/autogpt_platform/backend/backend/server/rest_api.py index 9214c00ab2..f18bd932f3 100644 --- a/autogpt_platform/backend/backend/server/rest_api.py +++ b/autogpt_platform/backend/backend/server/rest_api.py @@ -1,5 +1,6 @@ import contextlib import logging +from enum import Enum from typing import Any, Optional import autogpt_libs.auth.models @@ -14,6 +15,7 @@ from autogpt_libs.feature_flag.client import ( ) from autogpt_libs.logging.utils import generate_uvicorn_config from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute import backend.data.block import backend.data.db @@ -36,6 +38,7 @@ from backend.blocks.llm import LlmModel from backend.data.model import Credentials from backend.integrations.providers import ProviderName from backend.server.external.api import external_app +from backend.server.middleware.security import SecurityHeadersMiddleware settings = backend.util.settings.Settings() logger = logging.getLogger(__name__) @@ -67,6 +70,33 @@ async def lifespan_context(app: fastapi.FastAPI): await backend.data.db.disconnect() +def custom_generate_unique_id(route: APIRoute): + """Generate clean operation IDs for OpenAPI spec following the format: + {method}{tag}{summary} + """ + if not route.tags or not route.methods: + return f"{route.name}" + + method = list(route.methods)[0].lower() + first_tag = route.tags[0] + if isinstance(first_tag, Enum): + tag_str = first_tag.name + else: + tag_str = str(first_tag) + + tag = "".join(word.capitalize() for word in tag_str.split("_")) # v1/v2 + + summary = ( + route.summary if route.summary else route.name + ) # need to be unique, a different version could have the same summary + summary = "".join(word.capitalize() for word in str(summary).split("_")) + + if tag: + return f"{method}{tag}{summary}" + else: + return f"{method}{summary}" + + docs_url = ( "/docs" if settings.config.app_env == backend.util.settings.AppEnvironment.LOCAL @@ -82,8 +112,11 @@ app = fastapi.FastAPI( version="0.1", lifespan=lifespan_context, docs_url=docs_url, + generate_unique_id_function=custom_generate_unique_id, ) +app.add_middleware(SecurityHeadersMiddleware) + def handle_internal_http_error(status_code: int = 500, log_error: bool = True): def handler(request: fastapi.Request, exc: Exception): @@ -158,10 +191,12 @@ app.include_router( backend.server.v2.library.routes.router, tags=["v2"], prefix="/api/library" ) app.include_router( - backend.server.v2.otto.routes.router, tags=["v2"], prefix="/api/otto" + backend.server.v2.otto.routes.router, tags=["v2", "otto"], prefix="/api/otto" ) app.include_router( - backend.server.v2.turnstile.routes.router, tags=["v2"], prefix="/api/turnstile" + backend.server.v2.turnstile.routes.router, + tags=["v2", "turnstile"], + prefix="/api/turnstile", ) app.include_router( @@ -320,14 +355,14 @@ class AgentServer(backend.util.service.AppProcess): ) @staticmethod - def test_create_credentials( + async def test_create_credentials( user_id: str, provider: ProviderName, credentials: Credentials, ) -> Credentials: from backend.server.integrations.router import create_credentials - return create_credentials( + return await create_credentials( user_id=user_id, provider=provider, credentials=credentials ) diff --git a/autogpt_platform/backend/backend/server/routers/postmark/postmark.py b/autogpt_platform/backend/backend/server/routers/postmark/postmark.py index ae744bfc10..b83b77dc12 100644 --- a/autogpt_platform/backend/backend/server/routers/postmark/postmark.py +++ b/autogpt_platform/backend/backend/server/routers/postmark/postmark.py @@ -34,13 +34,13 @@ router = APIRouter() logger = logging.getLogger(__name__) -@router.post("/unsubscribe") +@router.post("/unsubscribe", summary="One Click Email Unsubscribe") async def unsubscribe_via_one_click(token: Annotated[str, Query()]): - logger.info(f"Received unsubscribe request from One Click Unsubscribe: {token}") + logger.info("Received unsubscribe request from One Click Unsubscribe") try: await unsubscribe_user_by_token(token) except Exception as e: - logger.exception("Unsubscribe token %s failed: %s", token, e) + logger.exception("Unsubscribe failed: %s", e) raise HTTPException( status_code=500, detail={"message": str(e), "hint": "Verify Postmark token settings."}, @@ -48,7 +48,11 @@ async def unsubscribe_via_one_click(token: Annotated[str, Query()]): return JSONResponse(status_code=200, content={"status": "ok"}) -@router.post("/", dependencies=[Depends(postmark_validator.get_dependency())]) +@router.post( + "/", + dependencies=[Depends(postmark_validator.get_dependency())], + summary="Handle Postmark Email Webhooks", +) async def postmark_webhook_handler( webhook: Annotated[ PostmarkWebhook, diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index d9d39e4666..e177d22d34 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -50,7 +50,6 @@ from backend.data.onboarding import ( onboarding_enabled, update_user_onboarding, ) -from backend.data.rabbitmq import AsyncRabbitMQ from backend.data.user import ( get_or_create_user, get_user_notification_preference, @@ -59,7 +58,6 @@ from backend.data.user import ( ) from backend.executor import scheduler from backend.executor import utils as execution_utils -from backend.executor.utils import create_execution_queue_config from backend.integrations.webhooks.graph_lifecycle_hooks import ( on_graph_activate, on_graph_deactivate, @@ -83,13 +81,6 @@ def execution_scheduler_client() -> scheduler.SchedulerClient: return get_service_client(scheduler.SchedulerClient, health_check=False) -@thread_cached -async def execution_queue_client() -> AsyncRabbitMQ: - client = AsyncRabbitMQ(create_execution_queue_config()) - await client.connect() - return client - - @thread_cached def execution_event_bus() -> AsyncRedisExecutionEventBus: return AsyncRedisExecutionEventBus() @@ -122,14 +113,22 @@ v1_router.include_router( ######################################################## -@v1_router.post("/auth/user", tags=["auth"], dependencies=[Depends(auth_middleware)]) +@v1_router.post( + "/auth/user", + summary="Get or create user", + tags=["auth"], + dependencies=[Depends(auth_middleware)], +) async def get_or_create_user_route(user_data: dict = Depends(auth_middleware)): user = await get_or_create_user(user_data) return user.model_dump() @v1_router.post( - "/auth/user/email", tags=["auth"], dependencies=[Depends(auth_middleware)] + "/auth/user/email", + summary="Update user email", + tags=["auth"], + dependencies=[Depends(auth_middleware)], ) async def update_user_email_route( user_id: Annotated[str, Depends(get_user_id)], email: str = Body(...) @@ -141,6 +140,7 @@ async def update_user_email_route( @v1_router.get( "/auth/user/preferences", + summary="Get notification preferences", tags=["auth"], dependencies=[Depends(auth_middleware)], ) @@ -153,6 +153,7 @@ async def get_preferences( @v1_router.post( "/auth/user/preferences", + summary="Update notification preferences", tags=["auth"], dependencies=[Depends(auth_middleware)], ) @@ -170,14 +171,20 @@ async def update_preferences( @v1_router.get( - "/onboarding", tags=["onboarding"], dependencies=[Depends(auth_middleware)] + "/onboarding", + summary="Get onboarding status", + tags=["onboarding"], + dependencies=[Depends(auth_middleware)], ) async def get_onboarding(user_id: Annotated[str, Depends(get_user_id)]): return await get_user_onboarding(user_id) @v1_router.patch( - "/onboarding", tags=["onboarding"], dependencies=[Depends(auth_middleware)] + "/onboarding", + summary="Update onboarding progress", + tags=["onboarding"], + dependencies=[Depends(auth_middleware)], ) async def update_onboarding( user_id: Annotated[str, Depends(get_user_id)], data: UserOnboardingUpdate @@ -187,6 +194,7 @@ async def update_onboarding( @v1_router.get( "/onboarding/agents", + summary="Get recommended agents", tags=["onboarding"], dependencies=[Depends(auth_middleware)], ) @@ -198,6 +206,7 @@ async def get_onboarding_agents( @v1_router.get( "/onboarding/enabled", + summary="Check onboarding enabled", tags=["onboarding", "public"], dependencies=[Depends(auth_middleware)], ) @@ -210,7 +219,12 @@ async def is_onboarding_enabled(): ######################################################## -@v1_router.get(path="/blocks", tags=["blocks"], dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/blocks", + summary="List available blocks", + tags=["blocks"], + dependencies=[Depends(auth_middleware)], +) def get_graph_blocks() -> Sequence[dict[Any, Any]]: blocks = [block() for block in get_blocks().values()] costs = get_block_costs() @@ -221,16 +235,17 @@ def get_graph_blocks() -> Sequence[dict[Any, Any]]: @v1_router.post( path="/blocks/{block_id}/execute", + summary="Execute graph block", tags=["blocks"], dependencies=[Depends(auth_middleware)], ) -def execute_graph_block(block_id: str, data: BlockInput) -> CompletedBlockOutput: +async def execute_graph_block(block_id: str, data: BlockInput) -> CompletedBlockOutput: obj = get_block(block_id) if not obj: raise HTTPException(status_code=404, detail=f"Block #{block_id} not found.") output = defaultdict(list) - for name, data in obj.execute(data): + async for name, data in obj.execute(data): output[name].append(data) return output @@ -240,7 +255,12 @@ def execute_graph_block(block_id: str, data: BlockInput) -> CompletedBlockOutput ######################################################## -@v1_router.get(path="/credits", dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/credits", + tags=["credits"], + summary="Get user credits", + dependencies=[Depends(auth_middleware)], +) async def get_user_credits( user_id: Annotated[str, Depends(get_user_id)], ) -> dict[str, int]: @@ -248,7 +268,10 @@ async def get_user_credits( @v1_router.post( - path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)] + path="/credits", + summary="Request credit top up", + tags=["credits"], + dependencies=[Depends(auth_middleware)], ) async def request_top_up( request: RequestTopUp, user_id: Annotated[str, Depends(get_user_id)] @@ -261,6 +284,7 @@ async def request_top_up( @v1_router.post( path="/credits/{transaction_key}/refund", + summary="Refund credit transaction", tags=["credits"], dependencies=[Depends(auth_middleware)], ) @@ -273,7 +297,10 @@ async def refund_top_up( @v1_router.patch( - path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)] + path="/credits", + summary="Fulfill checkout session", + tags=["credits"], + dependencies=[Depends(auth_middleware)], ) async def fulfill_checkout(user_id: Annotated[str, Depends(get_user_id)]): await _user_credit_model.fulfill_checkout(user_id=user_id) @@ -282,6 +309,7 @@ async def fulfill_checkout(user_id: Annotated[str, Depends(get_user_id)]): @v1_router.post( path="/credits/auto-top-up", + summary="Configure auto top up", tags=["credits"], dependencies=[Depends(auth_middleware)], ) @@ -310,6 +338,7 @@ async def configure_user_auto_top_up( @v1_router.get( path="/credits/auto-top-up", + summary="Get auto top up", tags=["credits"], dependencies=[Depends(auth_middleware)], ) @@ -319,7 +348,9 @@ async def get_user_auto_top_up( return await get_auto_top_up(user_id) -@v1_router.post(path="/credits/stripe_webhook", tags=["credits"]) +@v1_router.post( + path="/credits/stripe_webhook", summary="Handle Stripe webhooks", tags=["credits"] +) async def stripe_webhook(request: Request): # Get the raw request body payload = await request.body() @@ -354,14 +385,24 @@ async def stripe_webhook(request: Request): return Response(status_code=200) -@v1_router.get(path="/credits/manage", dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/credits/manage", + tags=["credits"], + summary="Manage payment methods", + dependencies=[Depends(auth_middleware)], +) async def manage_payment_method( user_id: Annotated[str, Depends(get_user_id)], ) -> dict[str, str]: return {"url": await _user_credit_model.create_billing_portal_session(user_id)} -@v1_router.get(path="/credits/transactions", dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/credits/transactions", + tags=["credits"], + summary="Get credit history", + dependencies=[Depends(auth_middleware)], +) async def get_credit_history( user_id: Annotated[str, Depends(get_user_id)], transaction_time: datetime | None = None, @@ -379,7 +420,12 @@ async def get_credit_history( ) -@v1_router.get(path="/credits/refunds", dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/credits/refunds", + tags=["credits"], + summary="Get refund requests", + dependencies=[Depends(auth_middleware)], +) async def get_refund_requests( user_id: Annotated[str, Depends(get_user_id)], ) -> list[RefundRequest]: @@ -395,7 +441,12 @@ class DeleteGraphResponse(TypedDict): version_counts: int -@v1_router.get(path="/graphs", tags=["graphs"], dependencies=[Depends(auth_middleware)]) +@v1_router.get( + path="/graphs", + summary="List user graphs", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], +) async def get_graphs( user_id: Annotated[str, Depends(get_user_id)], ) -> Sequence[graph_db.GraphModel]: @@ -403,10 +454,14 @@ async def get_graphs( @v1_router.get( - path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)] + path="/graphs/{graph_id}", + summary="Get specific graph", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], ) @v1_router.get( path="/graphs/{graph_id}/versions/{version}", + summary="Get graph version", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -430,6 +485,7 @@ async def get_graph( @v1_router.get( path="/graphs/{graph_id}/versions", + summary="Get all graph versions", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -443,7 +499,10 @@ async def get_graph_all_versions( @v1_router.post( - path="/graphs", tags=["graphs"], dependencies=[Depends(auth_middleware)] + path="/graphs", + summary="Create new graph", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], ) async def create_new_graph( create_graph: CreateGraph, @@ -466,7 +525,10 @@ async def create_new_graph( @v1_router.delete( - path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)] + path="/graphs/{graph_id}", + summary="Delete graph permanently", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], ) async def delete_graph( graph_id: str, user_id: Annotated[str, Depends(get_user_id)] @@ -478,7 +540,10 @@ async def delete_graph( @v1_router.put( - path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)] + path="/graphs/{graph_id}", + summary="Update graph version", + tags=["graphs"], + dependencies=[Depends(auth_middleware)], ) async def update_graph( graph_id: str, @@ -524,6 +589,7 @@ async def update_graph( @v1_router.put( path="/graphs/{graph_id}/versions/active", + summary="Set active graph version", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -562,6 +628,7 @@ async def set_graph_active_version( @v1_router.post( path="/graphs/{graph_id}/execute/{graph_version}", + summary="Execute graph agent", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -582,7 +649,7 @@ async def execute_graph( detail="Insufficient balance to execute the agent. Please top up your account.", ) - graph_exec = await execution_utils.add_graph_execution_async( + graph_exec = await execution_utils.add_graph_execution( graph_id=graph_id, user_id=user_id, inputs=inputs, @@ -595,18 +662,19 @@ async def execute_graph( @v1_router.post( path="/graphs/{graph_id}/executions/{graph_exec_id}/stop", + summary="Stop graph execution", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) async def stop_graph_run( - graph_exec_id: str, user_id: Annotated[str, Depends(get_user_id)] + graph_id: str, graph_exec_id: str, user_id: Annotated[str, Depends(get_user_id)] ) -> execution_db.GraphExecution: if not await execution_db.get_graph_execution_meta( user_id=user_id, execution_id=graph_exec_id ): raise HTTPException(404, detail=f"Agent execution #{graph_exec_id} not found") - await _cancel_execution(graph_exec_id) + await execution_utils.stop_graph_execution(graph_exec_id) # Retrieve & return canceled graph execution in its final state result = await execution_db.get_graph_execution( @@ -620,54 +688,9 @@ async def stop_graph_run( return result -async def _cancel_execution(graph_exec_id: str): - """ - Mechanism: - 1. Set the cancel event - 2. Graph executor's cancel handler thread detects the event, terminates workers, - reinitializes worker pool, and returns. - 3. Update execution statuses in DB and set `error` outputs to `"TERMINATED"`. - """ - queue_client = await execution_queue_client() - await queue_client.publish_message( - routing_key="", - message=execution_utils.CancelExecutionEvent( - graph_exec_id=graph_exec_id - ).model_dump_json(), - exchange=execution_utils.GRAPH_EXECUTION_CANCEL_EXCHANGE, - ) - - # Update the status of the graph execution - graph_execution = await execution_db.update_graph_execution_stats( - graph_exec_id, - execution_db.ExecutionStatus.TERMINATED, - ) - if graph_execution: - await execution_event_bus().publish(graph_execution) - - # Update the status of the node executions - node_execs = [ - node_exec.model_copy(update={"status": execution_db.ExecutionStatus.TERMINATED}) - for node_exec in await execution_db.get_node_executions( - graph_exec_id=graph_exec_id, - statuses=[ - execution_db.ExecutionStatus.QUEUED, - execution_db.ExecutionStatus.RUNNING, - execution_db.ExecutionStatus.INCOMPLETE, - ], - ) - ] - await execution_db.update_node_execution_status_batch( - [node_exec.node_exec_id for node_exec in node_execs], - execution_db.ExecutionStatus.TERMINATED, - ) - await asyncio.gather( - *[execution_event_bus().publish(node_exec) for node_exec in node_execs] - ) - - @v1_router.get( path="/executions", + summary="Get all executions", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -679,6 +702,7 @@ async def get_graphs_executions( @v1_router.get( path="/graphs/{graph_id}/executions", + summary="Get graph executions", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -691,6 +715,7 @@ async def get_graph_executions( @v1_router.get( path="/graphs/{graph_id}/executions/{graph_exec_id}", + summary="Get execution details", tags=["graphs"], dependencies=[Depends(auth_middleware)], ) @@ -720,6 +745,7 @@ async def get_graph_execution( @v1_router.delete( path="/executions/{graph_exec_id}", + summary="Delete graph execution", tags=["graphs"], dependencies=[Depends(auth_middleware)], status_code=HTTP_204_NO_CONTENT, @@ -747,6 +773,7 @@ class ScheduleCreationRequest(pydantic.BaseModel): @v1_router.post( path="/schedules", + summary="Create execution schedule", tags=["schedules"], dependencies=[Depends(auth_middleware)], ) @@ -774,6 +801,7 @@ async def create_schedule( @v1_router.delete( path="/schedules/{schedule_id}", + summary="Delete execution schedule", tags=["schedules"], dependencies=[Depends(auth_middleware)], ) @@ -787,6 +815,7 @@ async def delete_schedule( @v1_router.get( path="/schedules", + summary="List execution schedules", tags=["schedules"], dependencies=[Depends(auth_middleware)], ) @@ -807,6 +836,7 @@ async def get_execution_schedules( @v1_router.post( "/api-keys", + summary="Create new API key", response_model=CreateAPIKeyResponse, tags=["api-keys"], dependencies=[Depends(auth_middleware)], @@ -837,6 +867,7 @@ async def create_api_key( @v1_router.get( "/api-keys", + summary="List user API keys", response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], @@ -857,6 +888,7 @@ async def get_api_keys( @v1_router.get( "/api-keys/{key_id}", + summary="Get specific API key", response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], @@ -880,6 +912,7 @@ async def get_api_key( @v1_router.delete( "/api-keys/{key_id}", + summary="Revoke API key", response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], @@ -908,6 +941,7 @@ async def delete_api_key( @v1_router.post( "/api-keys/{key_id}/suspend", + summary="Suspend API key", response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], @@ -933,6 +967,7 @@ async def suspend_key( @v1_router.put( "/api-keys/{key_id}/permissions", + summary="Update key permissions", response_model=APIKeyWithoutHash, tags=["api-keys"], dependencies=[Depends(auth_middleware)], diff --git a/autogpt_platform/backend/backend/server/routers/v1_test.py b/autogpt_platform/backend/backend/server/routers/v1_test.py index 17c06b5778..cddee98273 100644 --- a/autogpt_platform/backend/backend/server/routers/v1_test.py +++ b/autogpt_platform/backend/backend/server/routers/v1_test.py @@ -137,10 +137,12 @@ def test_execute_graph_block( """Test execute block endpoint""" # Mock block mock_block = Mock() - mock_block.execute.return_value = [ - ("output1", {"data": "result1"}), - ("output2", {"data": "result2"}), - ] + + async def mock_execute(*args, **kwargs): + yield "output1", {"data": "result1"} + yield "output2", {"data": "result2"} + + mock_block.execute = mock_execute mocker.patch( "backend.server.routers.v1.get_block", diff --git a/autogpt_platform/backend/backend/server/v2/admin/credit_admin_routes.py b/autogpt_platform/backend/backend/server/v2/admin/credit_admin_routes.py index f19d8b45f0..009c541432 100644 --- a/autogpt_platform/backend/backend/server/v2/admin/credit_admin_routes.py +++ b/autogpt_platform/backend/backend/server/v2/admin/credit_admin_routes.py @@ -22,7 +22,9 @@ router = APIRouter( ) -@router.post("/add_credits", response_model=AddUserCreditsResponse) +@router.post( + "/add_credits", response_model=AddUserCreditsResponse, summary="Add Credits to User" +) async def add_user_credits( user_id: typing.Annotated[str, Body()], amount: typing.Annotated[int, Body()], @@ -49,6 +51,7 @@ async def add_user_credits( @router.get( "/users_history", response_model=UserHistoryResponse, + summary="Get All Users History", ) async def admin_get_all_user_history( admin_user: typing.Annotated[ diff --git a/autogpt_platform/backend/backend/server/v2/admin/store_admin_routes.py b/autogpt_platform/backend/backend/server/v2/admin/store_admin_routes.py index e59b0c4805..88f69360a4 100644 --- a/autogpt_platform/backend/backend/server/v2/admin/store_admin_routes.py +++ b/autogpt_platform/backend/backend/server/v2/admin/store_admin_routes.py @@ -19,6 +19,7 @@ router = fastapi.APIRouter(prefix="/admin", tags=["store", "admin"]) @router.get( "/listings", + summary="Get Admin Listings History", response_model=backend.server.v2.store.model.StoreListingsWithVersionsResponse, dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)], ) @@ -63,6 +64,7 @@ async def get_admin_listings_with_versions( @router.post( "/submissions/{store_listing_version_id}/review", + summary="Review Store Submission", response_model=backend.server.v2.store.model.StoreSubmission, dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)], ) @@ -104,6 +106,7 @@ async def review_submission( @router.get( "/submissions/download/{store_listing_version_id}", + summary="Admin Download Agent File", tags=["store", "admin"], dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)], ) diff --git a/autogpt_platform/backend/backend/server/v2/library/db.py b/autogpt_platform/backend/backend/server/v2/library/db.py index 15d479e588..b0f8f7a755 100644 --- a/autogpt_platform/backend/backend/server/v2/library/db.py +++ b/autogpt_platform/backend/backend/server/v2/library/db.py @@ -466,15 +466,15 @@ async def add_store_agent_to_library( # Create LibraryAgent entry added_agent = await prisma.models.LibraryAgent.prisma().create( - data=prisma.types.LibraryAgentCreateInput( - userId=user_id, - AgentGraph={ + data={ + "User": {"connect": {"id": user_id}}, + "AgentGraph": { "connect": { "graphVersionId": {"id": graph.id, "version": graph.version} } }, - isCreatedByUser=False, - ), + "isCreatedByUser": False, + }, include=library_agent_include(user_id), ) logger.debug( diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py index 9a0b19b56b..54a0cd235f 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/agents.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/agents.py @@ -20,6 +20,7 @@ router = APIRouter( @router.get( "", + summary="List Library Agents", responses={ 500: {"description": "Server error", "content": {"application/json": {}}}, }, @@ -77,7 +78,7 @@ async def list_library_agents( ) from e -@router.get("/{library_agent_id}") +@router.get("/{library_agent_id}", summary="Get Library Agent") async def get_library_agent( library_agent_id: str, user_id: str = Depends(autogpt_auth_lib.depends.get_user_id), @@ -87,6 +88,7 @@ async def get_library_agent( @router.get( "/marketplace/{store_listing_version_id}", + summary="Get Agent By Store ID", tags=["store, library"], response_model=library_model.LibraryAgent | None, ) @@ -118,6 +120,7 @@ async def get_library_agent_by_store_listing_version_id( @router.post( "", + summary="Add Marketplace Agent", status_code=status.HTTP_201_CREATED, responses={ 201: {"description": "Agent added successfully"}, @@ -180,6 +183,7 @@ async def add_marketplace_agent_to_library( @router.put( "/{library_agent_id}", + summary="Update Library Agent", status_code=status.HTTP_204_NO_CONTENT, responses={ 204: {"description": "Agent updated successfully"}, @@ -232,7 +236,7 @@ async def update_library_agent( ) from e -@router.post("/{library_agent_id}/fork") +@router.post("/{library_agent_id}/fork", summary="Fork Library Agent") async def fork_library_agent( library_agent_id: str, user_id: str = Depends(autogpt_auth_lib.depends.get_user_id), diff --git a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py index 504f46e088..23fc80d23e 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes/presets.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes/presets.py @@ -6,12 +6,14 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Query, status import backend.server.v2.library.db as db import backend.server.v2.library.model as models -from backend.executor.utils import add_graph_execution_async +from backend.executor.utils import add_graph_execution from backend.util.exceptions import NotFoundError logger = logging.getLogger(__name__) -router = APIRouter() +router = APIRouter( + tags=["presets"], +) @router.get( @@ -245,7 +247,7 @@ async def execute_preset( # Merge input overrides with preset inputs merged_node_input = preset.inputs | node_input - execution = await add_graph_execution_async( + execution = await add_graph_execution( graph_id=graph_id, user_id=user_id, inputs=merged_node_input, diff --git a/autogpt_platform/backend/backend/server/v2/otto/routes.py b/autogpt_platform/backend/backend/server/v2/otto/routes.py index d2574e35ca..0e2231f4bc 100644 --- a/autogpt_platform/backend/backend/server/v2/otto/routes.py +++ b/autogpt_platform/backend/backend/server/v2/otto/routes.py @@ -14,7 +14,10 @@ router = APIRouter() @router.post( - "/ask", response_model=ApiResponse, dependencies=[Depends(auth_middleware)] + "/ask", + response_model=ApiResponse, + dependencies=[Depends(auth_middleware)], + summary="Proxy Otto Chat Request", ) async def proxy_otto_request( request: ChatRequest, user_id: str = Depends(get_user_id) diff --git a/autogpt_platform/backend/backend/server/v2/store/image_gen.py b/autogpt_platform/backend/backend/server/v2/store/image_gen.py index ed1db82244..b75536d3cd 100644 --- a/autogpt_platform/backend/backend/server/v2/store/image_gen.py +++ b/autogpt_platform/backend/backend/server/v2/store/image_gen.py @@ -1,4 +1,3 @@ -import asyncio import io import logging from enum import Enum @@ -20,7 +19,7 @@ from backend.blocks.ideogram import ( from backend.data.graph import Graph from backend.data.model import CredentialsMetaInput, ProviderName from backend.integrations.credentials_store import ideogram_credentials -from backend.util.request import requests +from backend.util.request import Requests from backend.util.settings import Settings logger = logging.getLogger(__name__) @@ -37,12 +36,12 @@ class ImageStyle(str, Enum): async def generate_agent_image(agent: Graph | AgentGraph) -> io.BytesIO: if settings.config.use_agent_image_generation_v2: - return await asyncio.to_thread(generate_agent_image_v2, graph=agent) + return await generate_agent_image_v2(graph=agent) else: return await generate_agent_image_v1(agent=agent) -def generate_agent_image_v2(graph: Graph | AgentGraph) -> io.BytesIO: +async def generate_agent_image_v2(graph: Graph | AgentGraph) -> io.BytesIO: """ Generate an image for an agent using Ideogram model. Returns: @@ -74,7 +73,7 @@ def generate_agent_image_v2(graph: Graph | AgentGraph) -> io.BytesIO: ] # Run the Ideogram model block with the specified parameters - url = IdeogramModelBlock().run_once( + url = await IdeogramModelBlock().run_once( IdeogramModelBlock.Input( credentials=CredentialsMetaInput( id=ideogram_credentials.id, @@ -96,7 +95,8 @@ def generate_agent_image_v2(graph: Graph | AgentGraph) -> io.BytesIO: "result", credentials=ideogram_credentials, ) - return io.BytesIO(requests.get(url).content) + response = await Requests().get(url) + return io.BytesIO(response.content) async def generate_agent_image_v1(agent: Graph | AgentGraph) -> io.BytesIO: @@ -145,13 +145,13 @@ async def generate_agent_image_v1(agent: Graph | AgentGraph) -> io.BytesIO: else: # If it's a URL string, fetch the image bytes result_url = output[0] - response = requests.get(result_url) + response = await Requests().get(result_url) image_bytes = response.content elif isinstance(output, FileOutput): image_bytes = output.read() elif isinstance(output, str): # Output is a URL - response = requests.get(output) + response = await Requests().get(output) image_bytes = response.content else: raise RuntimeError("Unexpected output format from the model.") diff --git a/autogpt_platform/backend/backend/server/v2/store/routes.py b/autogpt_platform/backend/backend/server/v2/store/routes.py index 41795f5d88..96218d52a8 100644 --- a/autogpt_platform/backend/backend/server/v2/store/routes.py +++ b/autogpt_platform/backend/backend/server/v2/store/routes.py @@ -29,6 +29,7 @@ router = fastapi.APIRouter() @router.get( "/profile", + summary="Get user profile", tags=["store", "private"], response_model=backend.server.v2.store.model.ProfileDetails, ) @@ -61,6 +62,7 @@ async def get_profile( @router.post( "/profile", + summary="Update user profile", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=backend.server.v2.store.model.CreatorDetails, @@ -107,6 +109,7 @@ async def update_or_create_profile( @router.get( "/agents", + summary="List store agents", tags=["store", "public"], response_model=backend.server.v2.store.model.StoreAgentsResponse, ) @@ -179,6 +182,7 @@ async def get_agents( @router.get( "/agents/{username}/{agent_name}", + summary="Get specific agent", tags=["store", "public"], response_model=backend.server.v2.store.model.StoreAgentDetails, ) @@ -208,6 +212,7 @@ async def get_agent(username: str, agent_name: str): @router.get( "/graph/{store_listing_version_id}", + summary="Get agent graph", tags=["store"], ) async def get_graph_meta_by_store_listing_version_id( @@ -232,6 +237,7 @@ async def get_graph_meta_by_store_listing_version_id( @router.get( "/agents/{store_listing_version_id}", + summary="Get agent by version", tags=["store"], response_model=backend.server.v2.store.model.StoreAgentDetails, ) @@ -257,6 +263,7 @@ async def get_store_agent( @router.post( "/agents/{username}/{agent_name}/review", + summary="Create agent review", tags=["store"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=backend.server.v2.store.model.StoreReview, @@ -308,6 +315,7 @@ async def create_review( @router.get( "/creators", + summary="List store creators", tags=["store", "public"], response_model=backend.server.v2.store.model.CreatorsResponse, ) @@ -359,6 +367,7 @@ async def get_creators( @router.get( "/creator/{username}", + summary="Get creator details", tags=["store", "public"], response_model=backend.server.v2.store.model.CreatorDetails, ) @@ -390,6 +399,7 @@ async def get_creator( ############################################ @router.get( "/myagents", + summary="Get my agents", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=backend.server.v2.store.model.MyAgentsResponse, @@ -412,6 +422,7 @@ async def get_my_agents( @router.delete( "/submissions/{submission_id}", + summary="Delete store submission", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=bool, @@ -448,6 +459,7 @@ async def delete_submission( @router.get( "/submissions", + summary="List my submissions", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=backend.server.v2.store.model.StoreSubmissionsResponse, @@ -501,6 +513,7 @@ async def get_submissions( @router.post( "/submissions", + summary="Create store submission", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], response_model=backend.server.v2.store.model.StoreSubmission, @@ -548,6 +561,7 @@ async def create_submission( @router.post( "/submissions/media", + summary="Upload submission media", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], ) @@ -585,6 +599,7 @@ async def upload_submission_media( @router.post( "/submissions/generate_image", + summary="Generate submission image", tags=["store", "private"], dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)], ) @@ -646,6 +661,7 @@ async def generate_image( @router.get( "/download/agents/{store_listing_version_id}", + summary="Download agent file", tags=["store", "public"], ) async def download_agent_file( diff --git a/autogpt_platform/backend/backend/server/v2/turnstile/routes.py b/autogpt_platform/backend/backend/server/v2/turnstile/routes.py index d1803df584..7a4fe5bafa 100644 --- a/autogpt_platform/backend/backend/server/v2/turnstile/routes.py +++ b/autogpt_platform/backend/backend/server/v2/turnstile/routes.py @@ -13,7 +13,9 @@ router = APIRouter() settings = Settings() -@router.post("/verify", response_model=TurnstileVerifyResponse) +@router.post( + "/verify", response_model=TurnstileVerifyResponse, summary="Verify Turnstile Token" +) async def verify_turnstile_token( request: TurnstileVerifyRequest, ) -> TurnstileVerifyResponse: diff --git a/autogpt_platform/backend/backend/util/decorator.py b/autogpt_platform/backend/backend/util/decorator.py index 9047ea0b77..84f128333f 100644 --- a/autogpt_platform/backend/backend/util/decorator.py +++ b/autogpt_platform/backend/backend/util/decorator.py @@ -2,7 +2,7 @@ import functools import logging import os import time -from typing import Callable, ParamSpec, Tuple, TypeVar +from typing import Any, Awaitable, Callable, Coroutine, ParamSpec, Tuple, TypeVar from pydantic import BaseModel @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) def time_measured(func: Callable[P, T]) -> Callable[P, Tuple[TimingInfo, T]]: """ - Decorator to measure the time taken by a function to execute. + Decorator to measure the time taken by a synchronous function to execute. """ @functools.wraps(func) @@ -50,6 +50,28 @@ def time_measured(func: Callable[P, T]) -> Callable[P, Tuple[TimingInfo, T]]: return wrapper +def async_time_measured( + func: Callable[P, Awaitable[T]], +) -> Callable[P, Awaitable[Tuple[TimingInfo, T]]]: + """ + Decorator to measure the time taken by an async function to execute. + """ + + @functools.wraps(func) + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Tuple[TimingInfo, T]: + start_wall_time, start_cpu_time = _start_measurement() + try: + result = await func(*args, **kwargs) + finally: + wall_duration, cpu_duration = _end_measurement( + start_wall_time, start_cpu_time + ) + timing_info = TimingInfo(cpu_time=cpu_duration, wall_time=wall_duration) + return timing_info, result + + return async_wrapper + + def error_logged(func: Callable[P, T]) -> Callable[P, T | None]: """ Decorator to suppress and log any exceptions raised by a function. @@ -65,3 +87,22 @@ def error_logged(func: Callable[P, T]) -> Callable[P, T | None]: ) return wrapper + + +def async_error_logged( + func: Callable[P, Coroutine[Any, Any, T]], +) -> Callable[P, Coroutine[Any, Any, T | None]]: + """ + Decorator to suppress and log any exceptions raised by an async function. + """ + + @functools.wraps(func) + async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T | None: + try: + return await func(*args, **kwargs) + except Exception as e: + logger.exception( + f"Error when calling async function {func.__name__} with arguments {args} {kwargs}: {e}" + ) + + return wrapper diff --git a/autogpt_platform/backend/backend/util/file.py b/autogpt_platform/backend/backend/util/file.py index 5b876a3ec0..27ad4cdd19 100644 --- a/autogpt_platform/backend/backend/util/file.py +++ b/autogpt_platform/backend/backend/util/file.py @@ -7,7 +7,7 @@ import uuid from pathlib import Path from urllib.parse import urlparse -from backend.util.request import requests +from backend.util.request import Requests from backend.util.type import MediaFileType TEMP_DIR = Path(tempfile.gettempdir()).resolve() @@ -29,7 +29,7 @@ def clean_exec_files(graph_exec_id: str, file: str = "") -> None: shutil.rmtree(exec_path) -def store_media_file( +async def store_media_file( graph_exec_id: str, file: MediaFileType, return_content: bool = False ) -> MediaFileType: """ @@ -114,8 +114,7 @@ def store_media_file( target_path = _ensure_inside_base(base_path / filename, base_path) # Download and save - resp = requests.get(file) - resp.raise_for_status() + resp = await Requests().get(file) target_path.write_bytes(resp.content) else: diff --git a/autogpt_platform/backend/backend/util/logging.py b/autogpt_platform/backend/backend/util/logging.py index 40dfb52a9f..9b6a64deac 100644 --- a/autogpt_platform/backend/backend/util/logging.py +++ b/autogpt_platform/backend/backend/util/logging.py @@ -61,6 +61,7 @@ class TruncatedLogger: def _wrap(self, msg: str, **extra): extra_msg = str(extra or "") - if len(extra_msg) > 1000: - extra_msg = extra_msg[:1000] + "..." - return f"{self.prefix} {msg} {extra_msg}" + text = f"{self.prefix} {msg} {extra_msg}" + if len(text) > self.max_length: + text = text[: self.max_length] + "..." + return text diff --git a/autogpt_platform/backend/backend/util/metrics.py b/autogpt_platform/backend/backend/util/metrics.py index 3e1822fad0..f169887307 100644 --- a/autogpt_platform/backend/backend/util/metrics.py +++ b/autogpt_platform/backend/backend/util/metrics.py @@ -1,4 +1,3 @@ -import asyncio import logging import sentry_sdk @@ -31,7 +30,7 @@ def sentry_capture_error(error: Exception): sentry_sdk.flush() -def discord_send_alert(content: str): +async def discord_send_alert(content: str): from backend.blocks.discord import SendDiscordMessageBlock from backend.data.model import APIKeyCredentials, CredentialsMetaInput, ProviderName from backend.util.settings import Settings @@ -44,13 +43,7 @@ def discord_send_alert(content: str): expires_at=None, ) - try: - loop = asyncio.get_event_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - return SendDiscordMessageBlock().run_once( + return await SendDiscordMessageBlock().run_once( SendDiscordMessageBlock.Input( credentials=CredentialsMetaInput( id=creds.id, diff --git a/autogpt_platform/backend/backend/util/request.py b/autogpt_platform/backend/backend/util/request.py index 4dfd32a12a..9a4ee81392 100644 --- a/autogpt_platform/backend/backend/util/request.py +++ b/autogpt_platform/backend/backend/util/request.py @@ -1,17 +1,22 @@ +import asyncio import ipaddress import re import socket import ssl -from typing import Callable, Optional +from io import BytesIO +from typing import Any, Callable, Optional from urllib.parse import ParseResult as URL from urllib.parse import quote, urljoin, urlparse +import aiohttp import idna -import requests as req -from requests.adapters import HTTPAdapter -from urllib3 import PoolManager +from aiohttp import FormData, abc +from tenacity import retry, retry_if_result, wait_exponential_jitter -from backend.util.settings import Config +from backend.util.json import json + +# Retry status codes for which we will automatically retry the request +THROTTLE_RETRY_STATUS_CODES: set[int] = {429, 500, 502, 503, 504, 408} # List of IP networks to block BLOCKED_IP_NETWORKS = [ @@ -61,26 +66,61 @@ def _remove_insecure_headers(headers: dict, old_url: URL, new_url: URL) -> dict: return headers -class HostSSLAdapter(HTTPAdapter): +class HostResolver(abc.AbstractResolver): """ - A custom adapter that connects to an IP address but still + A custom resolver that connects to specified IP addresses but still sets the TLS SNI to the original host name so the cert can match. """ - def __init__(self, ssl_hostname, *args, **kwargs): + def __init__(self, ssl_hostname: str, ip_addresses: list[str]): self.ssl_hostname = ssl_hostname - super().__init__(*args, **kwargs) + self.ip_addresses = ip_addresses + self._default = aiohttp.AsyncResolver() - def init_poolmanager(self, *args, **kwargs): - self.poolmanager = PoolManager( - *args, - ssl_context=ssl.create_default_context(), - server_hostname=self.ssl_hostname, # This works for urllib3>=2 - **kwargs, - ) + async def resolve(self, host, port=0, family=socket.AF_INET): + if host == self.ssl_hostname: + results = [] + for ip in self.ip_addresses: + results.append( + { + "hostname": self.ssl_hostname, + "host": ip, + "port": port, + "family": family, + "proto": 0, + "flags": socket.AI_NUMERICHOST, + } + ) + return results + return await self._default.resolve(host, port, family) + + async def close(self): + await self._default.close() -def validate_url(url: str, trusted_origins: list[str]) -> tuple[URL, bool, list[str]]: +async def _resolve_host(hostname: str) -> list[str]: + """ + Resolves the hostname to a list of IP addresses (IPv4 first, then IPv6). + """ + loop = asyncio.get_running_loop() + try: + infos = await loop.getaddrinfo(hostname, None) + except socket.gaierror: + raise ValueError(f"Unable to resolve IP address for hostname {hostname}") + + ip_list = [info[4][0] for info in infos] + ipv4 = [ip for ip in ip_list if ":" not in ip] + ipv6 = [ip for ip in ip_list if ":" in ip] + ip_addresses = ipv4 + ipv6 + + if not ip_addresses: + raise ValueError(f"No IP addresses found for {hostname}") + return ip_addresses + + +async def validate_url( + url: str, trusted_origins: list[str] +) -> tuple[URL, bool, list[str]]: """ Validates the URL to prevent SSRF attacks by ensuring it does not point to a private, link-local, or otherwise blocked IP address — unless @@ -125,7 +165,7 @@ def validate_url(url: str, trusted_origins: list[str]) -> tuple[URL, bool, list[ ip_addresses: list[str] = [] if not is_trusted: # Resolve all IP addresses for the hostname - ip_addresses = _resolve_host(ascii_hostname) + ip_addresses = await _resolve_host(ascii_hostname) # Block any IP address that belongs to a blocked range for ip_str in ip_addresses: @@ -165,7 +205,9 @@ def pin_url(url: URL, ip_addresses: Optional[list[str]] = None) -> URL: if not ip_addresses: # Resolve all IP addresses for the hostname - ip_addresses = _resolve_host(url.hostname) + # (This call is blocking; ensure to call async _resolve_host before if possible) + ip_addresses = [] + # You may choose to raise or call synchronous resolve here; for simplicity, leave empty. # Pin to the first valid IP (for SSRF defense) pinned_ip = ip_addresses[0] @@ -189,23 +231,58 @@ def pin_url(url: URL, ip_addresses: Optional[list[str]] = None) -> URL: ) -def _resolve_host(hostname: str) -> list[str]: - try: - ip_list = [str(res[4][0]) for res in socket.getaddrinfo(hostname, None)] - ipv4 = [ip for ip in ip_list if ":" not in ip] - ipv6 = [ip for ip in ip_list if ":" in ip] - ip_addresses = ipv4 + ipv6 # Prefer IPv4 over IPv6 - except socket.gaierror: - raise ValueError(f"Unable to resolve IP address for hostname {hostname}") +ClientResponse = aiohttp.ClientResponse +ClientResponseError = aiohttp.ClientResponseError - if not ip_addresses: - raise ValueError(f"No IP addresses found for {hostname}") - return ip_addresses + +class Response: + """ + Buffered wrapper around aiohttp.ClientResponse that does *not* require + callers to manage connection or session lifetimes. + """ + + def __init__( + self, + *, + response: ClientResponse, + url: str, + body: bytes, + ): + self.status: int = response.status + self.headers = response.headers + self.reason: str | None = response.reason + self.request_info = response.request_info + self.url: str = url + self.content: bytes = body # raw bytes + + def json(self, encoding: str | None = None, **kwargs) -> dict: + """ + Parse the body as JSON and return the resulting Python object. + """ + return json.loads( + self.content.decode(encoding or "utf-8", errors="replace"), **kwargs + ) + + def text(self, encoding: str | None = None) -> str: + """ + Decode the body to a string. Encoding is guessed from the + Content-Type header if not supplied. + """ + if encoding is None: + # Try to extract charset from headers; fall back to UTF-8 + ctype = self.headers.get("content-type", "") + match = re.search(r"charset=([^\s;]+)", ctype, flags=re.I) + encoding = match.group(1) if match else None + return self.content.decode(encoding or "utf-8", errors="replace") + + @property + def ok(self) -> bool: + return 200 <= self.status < 300 class Requests: """ - A wrapper around the requests library that validates URLs before + A wrapper around an aiohttp ClientSession that validates URLs before making requests, preventing SSRF by blocking private networks and other disallowed address spaces. """ @@ -216,6 +293,7 @@ class Requests: raise_for_status: bool = True, extra_url_validator: Callable[[URL], URL] | None = None, extra_headers: dict[str, str] | None = None, + retry_max_wait: float = 300.0, ): self.trusted_origins = [] for url in trusted_origins or []: @@ -227,113 +305,196 @@ class Requests: self.raise_for_status = raise_for_status self.extra_url_validator = extra_url_validator self.extra_headers = extra_headers + self.retry_max_wait = retry_max_wait - def request( + async def request( self, - method, - url, - headers=None, - allow_redirects=True, - max_redirects=10, - *args, + method: str, + url: str, + *, + headers: Optional[dict] = None, + files: list[tuple[str, tuple[str, BytesIO, str]]] | None = None, + data: Any | None = None, + json: Any | None = None, + allow_redirects: bool = True, + max_redirects: int = 10, **kwargs, - ) -> req.Response: - # Validate URL and get trust status - url, is_trusted, ip_addresses = validate_url(url, self.trusted_origins) - - # Apply any extra user-defined validation/transformation - if self.extra_url_validator is not None: - url = self.extra_url_validator(url) - - # Pin the URL if untrusted - hostname = url.hostname - original_url = url.geturl() - if not is_trusted: - url = pin_url(url, ip_addresses) - - # Merge any extra headers - headers = dict(headers) if headers else {} - if self.extra_headers is not None: - headers.update(self.extra_headers) - - session = req.Session() - - # If untrusted, the hostname in the URL is replaced with the corresponding - # IP address, and we need to override the Host header with the actual hostname. - if url.hostname != hostname: - headers["Host"] = hostname - - # If hostname was untrusted and we replaced it by (pinned it to) its IP, - # we also need to attach a custom SNI adapter to make SSL work: - adapter = HostSSLAdapter(ssl_hostname=hostname) - session.mount("https://", adapter) - - # Perform the request with redirects disabled for manual handling - response = session.request( - method, - url.geturl(), - headers=headers, - allow_redirects=False, - *args, - **kwargs, + ) -> Response: + @retry( + wait=wait_exponential_jitter(max=self.retry_max_wait), + retry=retry_if_result(lambda r: r.status in THROTTLE_RETRY_STATUS_CODES), + reraise=True, ) - - # Replace response URLs with the original host for clearer error messages - if url.hostname != hostname: - response.url = original_url - if response.request is not None: - response.request.url = original_url - - if self.raise_for_status: - response.raise_for_status() - - # If allowed and a redirect is received, follow the redirect manually - if allow_redirects and response.is_redirect: - if max_redirects <= 0: - raise Exception("Too many redirects.") - - location = response.headers.get("Location") - if not location: - return response - - # The base URL is the pinned_url we just used - # so that relative redirects resolve correctly. - redirect_url = urlparse(urljoin(url.geturl(), location)) - # Carry forward the same headers but update Host - new_headers = _remove_insecure_headers(headers, url, redirect_url) - - return self.request( - method, - redirect_url.geturl(), - headers=new_headers, + async def _make_request() -> Response: + return await self._request( + method=method, + url=url, + headers=headers, + files=files, + data=data, + json=json, allow_redirects=allow_redirects, - max_redirects=max_redirects - 1, - *args, + max_redirects=max_redirects, **kwargs, ) - return response + return await _make_request() - def get(self, url, *args, **kwargs) -> req.Response: - return self.request("GET", url, *args, **kwargs) + async def _request( + self, + method: str, + url: str, + *, + headers: Optional[dict] = None, + files: list[tuple[str, tuple[str, BytesIO, str]]] | None = None, + data: Any | None = None, + json: Any | None = None, + allow_redirects: bool = True, + max_redirects: int = 10, + **kwargs, + ) -> Response: + if files is not None: + if json is not None: + raise ValueError( + "Cannot mix file uploads with JSON body; " + "use 'data' for extra form fields instead." + ) - def post(self, url, *args, **kwargs) -> req.Response: - return self.request("POST", url, *args, **kwargs) + form = FormData(quote_fields=False) + # add normal form fields first + if isinstance(data, dict): + for k, v in data.items(): + form.add_field(k, str(v)) + elif data is not None: + raise ValueError( + "When uploading files, 'data' must be a dict of form fields." + ) - def put(self, url, *args, **kwargs) -> req.Response: - return self.request("PUT", url, *args, **kwargs) + # add the file parts + for field_name, (filename, fh, content_type) in files: + form.add_field( + name=field_name, + value=fh, + filename=filename, + content_type=content_type or "application/octet-stream", + ) - def delete(self, url, *args, **kwargs) -> req.Response: - return self.request("DELETE", url, *args, **kwargs) + data = form - def head(self, url, *args, **kwargs) -> req.Response: - return self.request("HEAD", url, *args, **kwargs) + # Validate URL and get trust status + parsed_url, is_trusted, ip_addresses = await validate_url( + url, self.trusted_origins + ) - def options(self, url, *args, **kwargs) -> req.Response: - return self.request("OPTIONS", url, *args, **kwargs) + # Apply any extra user-defined validation/transformation + if self.extra_url_validator is not None: + parsed_url = self.extra_url_validator(parsed_url) - def patch(self, url, *args, **kwargs) -> req.Response: - return self.request("PATCH", url, *args, **kwargs) + # Pin the URL if untrusted + hostname = parsed_url.hostname + if hostname is None: + raise ValueError(f"Invalid URL: Unable to determine hostname of {url}") + original_url = parsed_url.geturl() + connector: Optional[aiohttp.TCPConnector] = None + if not is_trusted: + # Replace hostname with IP for connection but preserve SNI via resolver + resolver = HostResolver(ssl_hostname=hostname, ip_addresses=ip_addresses) + ssl_context = ssl.create_default_context() + connector = aiohttp.TCPConnector(resolver=resolver, ssl=ssl_context) + session_kwargs = {} + if connector: + session_kwargs["connector"] = connector -requests = Requests(trusted_origins=Config().trust_endpoints_for_requests) + # Merge any extra headers + req_headers = dict(headers) if headers else {} + if self.extra_headers is not None: + req_headers.update(self.extra_headers) + + # Override Host header if using IP connection + if connector: + req_headers["Host"] = hostname + + # Override data if files are provided + + async with aiohttp.ClientSession(**session_kwargs) as session: + # Perform the request with redirects disabled for manual handling + async with session.request( + method, + parsed_url.geturl(), + headers=req_headers, + allow_redirects=False, + data=data, + json=json, + **kwargs, + ) as response: + + if self.raise_for_status: + response.raise_for_status() + + # If allowed and a redirect is received, follow the redirect manually + if allow_redirects and response.status in (301, 302, 303, 307, 308): + if max_redirects <= 0: + raise Exception("Too many redirects.") + + location = response.headers.get("Location") + if not location: + return Response( + response=response, + url=original_url, + body=await response.read(), + ) + + # The base URL is the pinned_url we just used + # so that relative redirects resolve correctly. + redirect_url = urlparse(urljoin(parsed_url.geturl(), location)) + # Carry forward the same headers but update Host + new_headers = _remove_insecure_headers( + req_headers, parsed_url, redirect_url + ) + + return await self.request( + method, + redirect_url.geturl(), + headers=new_headers, + allow_redirects=allow_redirects, + max_redirects=max_redirects - 1, + files=files, + data=data, + json=json, + **kwargs, + ) + + # Reset response URL to original host for clarity + if parsed_url.hostname != hostname: + try: + response.url = original_url # type: ignore + except Exception: + pass + + return Response( + response=response, + url=original_url, + body=await response.read(), + ) + + async def get(self, url: str, *args, **kwargs) -> Response: + return await self.request("GET", url, *args, **kwargs) + + async def post(self, url: str, *args, **kwargs) -> Response: + return await self.request("POST", url, *args, **kwargs) + + async def put(self, url: str, *args, **kwargs) -> Response: + return await self.request("PUT", url, *args, **kwargs) + + async def delete(self, url: str, *args, **kwargs) -> Response: + return await self.request("DELETE", url, *args, **kwargs) + + async def head(self, url: str, *args, **kwargs) -> Response: + return await self.request("HEAD", url, *args, **kwargs) + + async def options(self, url: str, *args, **kwargs) -> Response: + return await self.request("OPTIONS", url, *args, **kwargs) + + async def patch(self, url: str, *args, **kwargs) -> Response: + return await self.request("PATCH", url, *args, **kwargs) diff --git a/autogpt_platform/backend/backend/util/service.py b/autogpt_platform/backend/backend/util/service.py index 91eef36def..abcfd75dd3 100644 --- a/autogpt_platform/backend/backend/util/service.py +++ b/autogpt_platform/backend/backend/util/service.py @@ -24,6 +24,12 @@ import httpx import uvicorn from fastapi import FastAPI, Request, responses from pydantic import BaseModel, TypeAdapter, create_model +from tenacity import ( + retry, + retry_if_exception_type, + stop_after_attempt, + wait_exponential_jitter, +) from backend.util.exceptions import InsufficientBalanceError from backend.util.json import to_dict @@ -248,9 +254,39 @@ def get_service_client( service_client_type: Type[ASC], call_timeout: int | None = api_call_timeout, health_check: bool = True, + request_retry: bool | int = False, ) -> ASC: + + def _maybe_retry(fn: Callable[..., R]) -> Callable[..., R]: + """Decorate *fn* with tenacity retry when enabled.""" + nonlocal request_retry + + if isinstance(request_retry, int): + retry_attempts = request_retry + request_retry = True + else: + retry_attempts = api_comm_retry + + if not request_retry: + return fn + + return retry( + reraise=True, + stop=stop_after_attempt(retry_attempts), + wait=wait_exponential_jitter(max=4.0), + retry=retry_if_exception_type( + ( + httpx.ConnectError, + httpx.ReadTimeout, + httpx.WriteTimeout, + httpx.ConnectTimeout, + httpx.RemoteProtocolError, + ) + ), + )(fn) + class DynamicClient: - def __init__(self): + def __init__(self) -> None: service_type = service_client_type.get_service_type() host = service_type.get_host() port = service_type.get_port() @@ -271,7 +307,7 @@ def get_service_client( ) def _handle_call_method_response( - self, response: httpx.Response, method_name: str + self, *, response: httpx.Response, method_name: str ) -> Any: try: response.raise_for_status() @@ -284,13 +320,15 @@ def get_service_client( *(error.args or [str(e)]) ) - def _call_method_sync(self, method_name: str, **kwargs) -> Any: + @_maybe_retry + def _call_method_sync(self, method_name: str, **kwargs: Any) -> Any: return self._handle_call_method_response( method_name=method_name, response=self.sync_client.post(method_name, json=to_dict(kwargs)), ) - async def _call_method_async(self, method_name: str, **kwargs) -> Any: + @_maybe_retry + async def _call_method_async(self, method_name: str, **kwargs: Any) -> Any: return self._handle_call_method_response( method_name=method_name, response=await self.async_client.post( @@ -298,17 +336,19 @@ def get_service_client( ), ) - async def aclose(self): + async def aclose(self) -> None: self.sync_client.close() await self.async_client.aclose() - def close(self): + def close(self) -> None: self.sync_client.close() - def _get_params(self, signature: inspect.Signature, *args, **kwargs) -> dict: + def _get_params( + self, signature: inspect.Signature, *args: Any, **kwargs: Any + ) -> dict[str, Any]: if args: arg_names = list(signature.parameters.keys()) - if arg_names[0] in ("self", "cls"): + if arg_names and arg_names[0] in ("self", "cls"): arg_names = arg_names[1:] kwargs.update(dict(zip(arg_names, args))) return kwargs @@ -324,35 +364,34 @@ def get_service_client( raise AttributeError( f"Method {name} not found in {service_client_type}" ) - else: - name = original_func.__name__ + rpc_name = original_func.__name__ sig = inspect.signature(original_func) ret_ann = sig.return_annotation - if ret_ann != inspect.Signature.empty: - expected_return = TypeAdapter(ret_ann) - else: - expected_return = None + expected_return = ( + None if ret_ann is inspect.Signature.empty else TypeAdapter(ret_ann) + ) if inspect.iscoroutinefunction(original_func): - async def async_method(*args, **kwargs) -> Any: + async def async_method(*args: P.args, **kwargs: P.kwargs): params = self._get_params(sig, *args, **kwargs) - result = await self._call_method_async(name, **params) + result = await self._call_method_async(rpc_name, **params) return self._get_return(expected_return, result) return async_method + else: - def sync_method(*args, **kwargs) -> Any: + def sync_method(*args: P.args, **kwargs: P.kwargs): params = self._get_params(sig, *args, **kwargs) - result = self._call_method_sync(name, **params) + result = self._call_method_sync(rpc_name, **params) return self._get_return(expected_return, result) return sync_method client = cast(ASC, DynamicClient()) - if health_check: + if health_check and hasattr(client, "health_check"): client.health_check() return client diff --git a/autogpt_platform/backend/backend/util/settings.py b/autogpt_platform/backend/backend/util/settings.py index c349f0c804..b2691cfd18 100644 --- a/autogpt_platform/backend/backend/util/settings.py +++ b/autogpt_platform/backend/backend/util/settings.py @@ -59,12 +59,6 @@ class Config(UpdateTrackingModel["Config"], BaseSettings): le=1000, description="Maximum number of workers to use for graph execution.", ) - num_node_workers: int = Field( - default=5, - ge=1, - le=1000, - description="Maximum number of workers to use for node execution within a single graph.", - ) pyro_host: str = Field( default="localhost", description="The default hostname of the Pyro server.", diff --git a/autogpt_platform/backend/backend/util/test.py b/autogpt_platform/backend/backend/util/test.py index 06a1f44f63..72a39d4027 100644 --- a/autogpt_platform/backend/backend/util/test.py +++ b/autogpt_platform/backend/backend/util/test.py @@ -1,3 +1,4 @@ +import inspect import logging import time import uuid @@ -93,7 +94,7 @@ async def wait_execution( assert False, "Execution did not complete in time." -def execute_block_test(block: Block): +async def execute_block_test(block: Block): prefix = f"[Test-{block.name}]" if not block.test_input or not block.test_output: @@ -110,10 +111,25 @@ def execute_block_test(block: Block): for mock_name, mock_obj in (block.test_mock or {}).items(): log.info(f"{prefix} mocking {mock_name}...") - if hasattr(block, mock_name): - setattr(block, mock_name, mock_obj) - else: + # check whether the field mock_name is an async function or not + if not hasattr(block, mock_name): log.info(f"{prefix} mock {mock_name} not found in block") + continue + + fun = getattr(block, mock_name) + is_async = inspect.iscoroutinefunction(fun) or inspect.isasyncgenfunction(fun) + + if is_async: + + async def async_mock( + *args, _mock_name=mock_name, _mock_obj=mock_obj, **kwargs + ): + return _mock_obj(*args, **kwargs) + + setattr(block, mock_name, async_mock) + + else: + setattr(block, mock_name, mock_obj) # Populate credentials argument(s) extra_exec_kwargs: dict = { @@ -141,7 +157,9 @@ def execute_block_test(block: Block): for input_data in block.test_input: log.info(f"{prefix} in: {input_data}") - for output_name, output_data in block.execute(input_data, **extra_exec_kwargs): + async for output_name, output_data in block.execute( + input_data, **extra_exec_kwargs + ): if output_index >= len(block.test_output): raise ValueError( f"{prefix} produced output more than expected {output_index} >= {len(block.test_output)}:\nOutput Expected:\t\t{block.test_output}\nFailed Output Produced:\t('{output_name}', {output_data})\nNote that this may not be the one that was unexpected, but it is the first that triggered the extra output warning" diff --git a/autogpt_platform/backend/migrations/20250620000924_make_data_nullable/migration.sql b/autogpt_platform/backend/migrations/20250620000924_make_data_nullable/migration.sql new file mode 100644 index 0000000000..54a29f3214 --- /dev/null +++ b/autogpt_platform/backend/migrations/20250620000924_make_data_nullable/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "AgentNodeExecutionInputOutput" ALTER COLUMN "data" DROP NOT NULL; diff --git a/autogpt_platform/backend/poetry.lock b/autogpt_platform/backend/poetry.lock index 00411f1535..4ea22878d4 100644 --- a/autogpt_platform/backend/poetry.lock +++ b/autogpt_platform/backend/poetry.lock @@ -17,6 +17,33 @@ aiormq = ">=6.8,<6.9" exceptiongroup = ">=1,<2" yarl = "*" +[[package]] +name = "aiodns" +version = "3.4.0" +description = "Simple DNS resolver for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiodns-3.4.0-py3-none-any.whl", hash = "sha256:4da2b25f7475343f3afbb363a2bfe46afa544f2b318acb9a945065e622f4ed24"}, + {file = "aiodns-3.4.0.tar.gz", hash = "sha256:24b0ae58410530367f21234d0c848e4de52c1f16fbddc111726a4ab536ec1b2f"}, +] + +[package.dependencies] +pycares = ">=4.0.0" + +[[package]] +name = "aiofiles" +version = "24.1.0" +description = "File support for asyncio." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"}, + {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"}, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -303,13 +330,16 @@ develop = true [package.dependencies] colorama = "^0.4.6" expiringdict = "^1.2.2" +fastapi = "^0.115.12" google-cloud-logging = "^3.12.1" +launchdarkly-server-sdk = "^9.11.1" pydantic = "^2.11.4" pydantic-settings = "^2.9.1" pyjwt = "^2.10.1" pytest-asyncio = "^0.26.0" pytest-mock = "^3.14.0" supabase = "^2.15.1" +uvicorn = "^0.34.3" [package.source] type = "directory" @@ -3739,6 +3769,93 @@ files = [ [package.dependencies] pyasn1 = ">=0.6.1,<0.7.0" +[[package]] +name = "pycares" +version = "4.8.0" +description = "Python interface for c-ares" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pycares-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f40d9f4a8de398b110fdf226cdfadd86e8c7eb71d5298120ec41cf8d94b0012f"}, + {file = "pycares-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:339de06fc849a51015968038d2bbed68fc24047522404af9533f32395ca80d25"}, + {file = "pycares-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372a236c1502b9056b0bea195c64c329603b4efa70b593a33b7ae37fbb7fad00"}, + {file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03f66a5e143d102ccc204bd4e29edd70bed28420f707efd2116748241e30cb73"}, + {file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef50504296cd5fc58cfd6318f82e20af24fbe2c83004f6ff16259adb13afdf14"}, + {file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1bc541b627c7951dd36136b18bd185c5244a0fb2af5b1492ffb8acaceec1c5b"}, + {file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:938d188ed6bed696099be67ebdcdf121827b9432b17a9ea9e40dc35fd9d85363"}, + {file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:327837ffdc0c7adda09c98e1263c64b2aff814eea51a423f66733c75ccd9a642"}, + {file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a6b9b8d08c4508c45bd39e0c74e9e7052736f18ca1d25a289365bb9ac36e5849"}, + {file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:feac07d5e6d2d8f031c71237c21c21b8c995b41a1eba64560e8cf1e42ac11bc6"}, + {file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5bcdbf37012fd2323ca9f2a1074421a9ccf277d772632f8f0ce8c46ec7564250"}, + {file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e3ebb692cb43fcf34fe0d26f2cf9a0ea53fdfb136463845b81fad651277922db"}, + {file = "pycares-4.8.0-cp310-cp310-win32.whl", hash = "sha256:d98447ec0efff3fa868ccc54dcc56e71faff498f8848ecec2004c3108efb4da2"}, + {file = "pycares-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:1abb8f40917960ead3c2771277f0bdee1967393b0fdf68743c225b606787da68"}, + {file = "pycares-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e25db89005ddd8d9c5720293afe6d6dd92e682fc6bc7a632535b84511e2060d"}, + {file = "pycares-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f9665ef116e6ee216c396f5f927756c2164f9f3316aec7ff1a9a1e1e7ec9b2a"}, + {file = "pycares-4.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54a96893133471f6889b577147adcc21a480dbe316f56730871028379c8313f3"}, + {file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51024b3a69762bd3100d94986a29922be15e13f56f991aaefb41f5bcd3d7f0bb"}, + {file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47ff9db50c599e4d965ae3bec99cc30941c1d2b0f078ec816680b70d052dd54a"}, + {file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27ef8ff4e0f60ea6769a60d1c3d1d2aefed1d832e7bb83fc3934884e2dba5cdd"}, + {file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63511af7a3f9663f562fbb6bfa3591a259505d976e2aba1fa2da13dde43c6ca7"}, + {file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:73c3219b47616e6a5ad1810de96ed59721c7751f19b70ae7bf24997a8365408f"}, + {file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:da42a45207c18f37be5e491c14b6d1063cfe1e46620eb661735d0cedc2b59099"}, + {file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8a068e898bb5dd09cd654e19cd2abf20f93d0cc59d5d955135ed48ea0f806aa1"}, + {file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:962aed95675bb66c0b785a2fbbd1bb58ce7f009e283e4ef5aaa4a1f2dc00d217"}, + {file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce8b1a16c1e4517a82a0ebd7664783a327166a3764d844cf96b1fb7b9dd1e493"}, + {file = "pycares-4.8.0-cp311-cp311-win32.whl", hash = "sha256:b3749ddbcbd216376c3b53d42d8b640b457133f1a12b0e003f3838f953037ae7"}, + {file = "pycares-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:5ce8a4e1b485b2360ab666c4ea1db97f57ede345a3b566d80bfa52b17e616610"}, + {file = "pycares-4.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3273e01a75308ed06d2492d83c7ba476e579a60a24d9f20fe178ce5e9d8d028b"}, + {file = "pycares-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fcedaadea1f452911fd29935749f98d144dae758d6003b7e9b6c5d5bd47d1dff"}, + {file = "pycares-4.8.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aae6cb33e287e06a4aabcbc57626df682c9a4fa8026207f5b498697f1c2fb562"}, + {file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25038b930e5be82839503fb171385b2aefd6d541bc5b7da0938bdb67780467d2"}, + {file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc8499b6e7dfbe4af65f6938db710ce9acd1debf34af2cbb93b898b1e5da6a5a"}, + {file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4e1c6a68ef56a7622f6176d9946d4e51f3c853327a0123ef35a5380230c84cd"}, + {file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7cc8c3c9114b9c84e4062d25ca9b4bddc80a65d0b074c7cb059275273382f89"}, + {file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4404014069d3e362abf404c9932d4335bb9c07ba834cfe7d683c725b92e0f9da"}, + {file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ee0a58c32ec2a352cef0e1d20335a7caf9871cd79b73be2ca2896fe70f09c9d7"}, + {file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:35f32f52b486b8fede3cbebf088f30b01242d0321b5216887c28e80490595302"}, + {file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ecbb506e27a3b3a2abc001c77beeccf265475c84b98629a6b3e61bd9f2987eaa"}, + {file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9392b2a34adbf60cb9e38f4a0d363413ecea8d835b5a475122f50f76676d59dd"}, + {file = "pycares-4.8.0-cp312-cp312-win32.whl", hash = "sha256:f0fbefe68403ffcff19c869b8d621c88a6d2cef18d53cf0dab0fa9458a6ca712"}, + {file = "pycares-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa8aab6085a2ddfb1b43a06ddf1b498347117bb47cd620d9b12c43383c9c2737"}, + {file = "pycares-4.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:358a9a2c6fed59f62788e63d88669224955443048a1602016d4358e92aedb365"}, + {file = "pycares-4.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e3e1278967fa8d4a0056be3fcc8fc551b8bad1fc7d0e5172196dccb8ddb036a"}, + {file = "pycares-4.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79befb773e370a8f97de9f16f5ea2c7e7fa0e3c6c74fbea6d332bf58164d7d06"}, + {file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b00d3695db64ce98a34e632e1d53f5a1cdb25451489f227bec2a6c03ff87ee8"}, + {file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37bdc4f2ff0612d60fc4f7547e12ff02cdcaa9a9e42e827bb64d4748994719f1"}, + {file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd92c44498ec7a6139888b464b28c49f7ba975933689bd67ea8d572b94188404"}, + {file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2665a0d810e2bbc41e97f3c3e5ea7950f666b3aa19c5f6c99d6b018ccd2e0052"}, + {file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45a629a6470a33478514c566bce50c63f1b17d1c5f2f964c9a6790330dc105fb"}, + {file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:47bb378f1773f41cca8e31dcdf009ce4a9b8aff8a30c7267aaff9a099c407ba5"}, + {file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3feae38458005cc101956e38f16eb3145fff8cd793e35cd4bdef6bf1aa2623"}, + {file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:14bc28aeaa66b0f4331ac94455e8043c8a06b3faafd78cc49d4b677bae0d0b08"}, + {file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62c82b871470f2864a1febf7b96bb1d108ce9063e6d3d43727e8a46f0028a456"}, + {file = "pycares-4.8.0-cp313-cp313-win32.whl", hash = "sha256:01afa8964c698c8f548b46d726f766aa7817b2d4386735af1f7996903d724920"}, + {file = "pycares-4.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:22f86f81b12ab17b0a7bd0da1e27938caaed11715225c1168763af97f8bb51a7"}, + {file = "pycares-4.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:61325d13a95255e858f42a7a1a9e482ff47ef2233f95ad9a4f308a3bd8ecf903"}, + {file = "pycares-4.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfec3a7d42336fa46a1e7e07f67000fd4b97860598c59a894c08f81378629e4e"}, + {file = "pycares-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65067e4b4f5345688817fff6be06b9b1f4ec3619b0b9ecc639bc681b73f646b"}, + {file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0322ad94bbaa7016139b5bbdcd0de6f6feb9d146d69e03a82aaca342e06830a6"}, + {file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:456c60f170c997f9a43c7afa1085fced8efb7e13ae49dd5656f998ae13c4bdb4"}, + {file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57a2c4c9ce423a85b0e0227409dbaf0d478f5e0c31d9e626768e77e1e887d32f"}, + {file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478d9c479108b7527266864c0affe3d6e863492c9bc269217e36100c8fd89b91"}, + {file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aed56bca096990ca0aa9bbf95761fc87e02880e04b0845922b5c12ea9abe523f"}, + {file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ef265a390928ee2f77f8901c2273c53293157860451ad453ce7f45dd268b72f9"}, + {file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a5f17d7a76d8335f1c90a8530c8f1e8bb22e9a1d70a96f686efaed946de1c908"}, + {file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:891f981feb2ef34367378f813fc17b3d706ce95b6548eeea0c9fe7705d7e54b1"}, + {file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4102f6d9117466cc0a1f527907a1454d109cc9e8551b8074888071ef16050fe3"}, + {file = "pycares-4.8.0-cp39-cp39-win32.whl", hash = "sha256:d6775308659652adc88c82c53eda59b5e86a154aaba5ad1e287bbb3e0be77076"}, + {file = "pycares-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:8bc05462aa44788d48544cca3d2532466fed2cdc5a2f24a43a92b620a61c9d19"}, + {file = "pycares-4.8.0.tar.gz", hash = "sha256:2fc2ebfab960f654b3e3cf08a732486950da99393a657f8b44618ad3ed2d39c1"}, +] + +[package.dependencies] +cffi = ">=1.5.0" + +[package.extras] +idna = ["idna (>=2.1)"] + [[package]] name = "pycodestyle" version = "2.13.0" @@ -6252,4 +6369,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "8d59c154b4ec91c28424c552de7c85c6399efe24ab74a979bfd62275d112fbf0" +content-hash = "6c93e51cf22c06548aa6d0e23ca8ceb4450f5e02d4142715e941aabc1a2cbd6a" diff --git a/autogpt_platform/backend/pyproject.toml b/autogpt_platform/backend/pyproject.toml index b54ba12b23..26c05668c0 100644 --- a/autogpt_platform/backend/pyproject.toml +++ b/autogpt_platform/backend/pyproject.toml @@ -10,6 +10,7 @@ packages = [{ include = "backend", format = "sdist" }] [tool.poetry.dependencies] python = ">=3.10,<3.13" aio-pika = "^9.5.5" +aiodns = "^3.1.1" anthropic = "^0.51.0" apscheduler = "^3.11.0" autogpt-libs = { path = "../autogpt_libs", develop = true } @@ -66,6 +67,7 @@ youtube-transcript-api = "^0.6.2" zerobouncesdk = "^1.1.1" # NOTE: please insert new dependencies in their alphabetical location pytest-snapshot = "^0.9.0" +aiofiles = "^24.1.0" [tool.poetry.group.dev.dependencies] aiohappyeyeballs = "^2.6.1" diff --git a/autogpt_platform/backend/schema.prisma b/autogpt_platform/backend/schema.prisma index a20a537fcb..58e724db9c 100644 --- a/autogpt_platform/backend/schema.prisma +++ b/autogpt_platform/backend/schema.prisma @@ -80,8 +80,8 @@ enum OnboardingStep { } model UserOnboarding { - id String @id @default(uuid()) - createdAt DateTime @default(now()) + id String @id @default(uuid()) + createdAt DateTime @default(now()) updatedAt DateTime? @updatedAt completedSteps OnboardingStep[] @default([]) @@ -122,7 +122,7 @@ model AgentGraph { forkedFromId String? forkedFromVersion Int? - forkedFrom AgentGraph? @relation("AgentGraphForks", fields: [forkedFromId, forkedFromVersion], references: [id, version]) + forkedFrom AgentGraph? @relation("AgentGraphForks", fields: [forkedFromId, forkedFromVersion], references: [id, version]) forks AgentGraph[] @relation("AgentGraphForks") Nodes AgentNode[] @@ -390,7 +390,7 @@ model AgentNodeExecutionInputOutput { id String @id @default(uuid()) name String - data Json + data Json? time DateTime @default(now()) // Prisma requires explicit back-references. diff --git a/autogpt_platform/backend/test/block/test_block.py b/autogpt_platform/backend/test/block/test_block.py index 48d2616f61..3f4aedb1e9 100644 --- a/autogpt_platform/backend/test/block/test_block.py +++ b/autogpt_platform/backend/test/block/test_block.py @@ -7,5 +7,5 @@ from backend.util.test import execute_block_test @pytest.mark.parametrize("block", get_blocks().values(), ids=lambda b: b.name) -def test_available_blocks(block: Type[Block]): - execute_block_test(block()) +async def test_available_blocks(block: Type[Block]): + await execute_block_test(block()) diff --git a/autogpt_platform/backend/test/executor/test_tool_use.py b/autogpt_platform/backend/test/executor/test_tool_use.py index d93797633b..54ecfbce0a 100644 --- a/autogpt_platform/backend/test/executor/test_tool_use.py +++ b/autogpt_platform/backend/test/executor/test_tool_use.py @@ -22,11 +22,11 @@ async def create_graph(s: SpinTestServer, g: graph.Graph, u: User) -> graph.Grap return await s.agent_server.test_create_graph(CreateGraph(graph=g), u.id) -def create_credentials(s: SpinTestServer, u: User): +async def create_credentials(s: SpinTestServer, u: User): provider = ProviderName.OPENAI credentials = llm.TEST_CREDENTIALS try: - s.agent_server.test_create_credentials(u.id, provider, credentials) + await s.agent_server.test_create_credentials(u.id, provider, credentials) except Exception: # ValueErrors is raised trying to recreate the same credentials # so hidding the error @@ -65,7 +65,7 @@ async def execute_graph( async def test_graph_validation_with_tool_nodes_correct(server: SpinTestServer): test_user = await create_test_user() test_tool_graph = await create_graph(server, create_test_graph(), test_user) - create_credentials(server, test_user) + await create_credentials(server, test_user) nodes = [ graph.Node( @@ -116,7 +116,7 @@ async def test_graph_validation_with_tool_nodes_raises_error(server: SpinTestSer test_user = await create_test_user() test_tool_graph = await create_graph(server, create_test_graph(), test_user) - create_credentials(server, test_user) + await create_credentials(server, test_user) nodes = [ graph.Node( @@ -176,7 +176,7 @@ async def test_graph_validation_with_tool_nodes_raises_error(server: SpinTestSer async def test_smart_decision_maker_function_signature(server: SpinTestServer): test_user = await create_test_user() test_tool_graph = await create_graph(server, create_test_graph(), test_user) - create_credentials(server, test_user) + await create_credentials(server, test_user) nodes = [ graph.Node( diff --git a/autogpt_platform/backend/test/util/test_request.py b/autogpt_platform/backend/test/util/test_request.py index e973e7dd6e..57717ff77f 100644 --- a/autogpt_platform/backend/test/util/test_request.py +++ b/autogpt_platform/backend/test/util/test_request.py @@ -54,14 +54,14 @@ from backend.util.request import pin_url, validate_url ("example.com?param=äöü", [], "http://example.com?param=äöü", False), ], ) -def test_validate_url_no_dns_rebinding( +async def test_validate_url_no_dns_rebinding( raw_url: str, trusted_origins: list[str], expected_value: str, should_raise: bool ): if should_raise: with pytest.raises(ValueError): - validate_url(raw_url, trusted_origins) + await validate_url(raw_url, trusted_origins) else: - validated_url, _, _ = validate_url(raw_url, trusted_origins) + validated_url, _, _ = await validate_url(raw_url, trusted_origins) assert validated_url.geturl() == expected_value @@ -78,7 +78,7 @@ def test_validate_url_no_dns_rebinding( ("blocked.com", ["127.0.0.1"], True, None), ], ) -def test_dns_rebinding_fix( +async def test_dns_rebinding_fix( monkeypatch, hostname: str, resolved_ips: list[str], @@ -100,10 +100,10 @@ def test_dns_rebinding_fix( if expect_error: # If any IP is blocked, we expect a ValueError with pytest.raises(ValueError): - url, _, ip_addresses = validate_url(hostname, []) + url, _, ip_addresses = await validate_url(hostname, []) pin_url(url, ip_addresses) else: - url, _, ip_addresses = validate_url(hostname, []) + url, _, ip_addresses = await validate_url(hostname, []) pinned_url = pin_url(url, ip_addresses).geturl() # The pinned_url should contain the first valid IP assert pinned_url.startswith("http://") or pinned_url.startswith("https://") diff --git a/autogpt_platform/frontend/.env.example b/autogpt_platform/frontend/.env.example index e3763c1da0..0660979013 100644 --- a/autogpt_platform/frontend/.env.example +++ b/autogpt_platform/frontend/.env.example @@ -8,6 +8,8 @@ NEXT_PUBLIC_LAUNCHDARKLY_ENABLED=false NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID= NEXT_PUBLIC_APP_ENV=local +NEXT_PUBLIC_AGPT_SERVER_BASE_URL=http://localhost:8006 + ## Locale settings NEXT_PUBLIC_DEFAULT_LOCALE=en @@ -33,3 +35,6 @@ NEXT_PUBLIC_SHOW_BILLING_PAGE=false ## This is the frontend site key NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY= NEXT_PUBLIC_TURNSTILE=disabled + +# Devtools +NEXT_PUBLIC_REACT_QUERY_DEVTOOL=true diff --git a/autogpt_platform/frontend/.eslintrc.json b/autogpt_platform/frontend/.eslintrc.json index bb8b1c099d..04a3f05d0f 100644 --- a/autogpt_platform/frontend/.eslintrc.json +++ b/autogpt_platform/frontend/.eslintrc.json @@ -1,3 +1,26 @@ { - "extends": ["next/core-web-vitals", "plugin:storybook/recommended"] + "extends": [ + "next/core-web-vitals", + "next/typescript", + "plugin:storybook/recommended", + "plugin:@tanstack/query/recommended" + ], + "rules": { + // Disabling exhaustive-deps to avoid forcing unnecessary dependencies and useCallback proliferation. + // We rely on code review for proper dependency management instead of mechanical rule following. + // See: https://kentcdodds.com/blog/usememo-and-usecallback + "react-hooks/exhaustive-deps": "off", + // Disable temporarily as we have some `any` in the codebase and we need to got case by case + // and see if they can be fixed. + "@typescript-eslint/no-explicit-any": "off", + // Allow unused vars that start with underscore (convention for intentionally unused) + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + } } diff --git a/autogpt_platform/frontend/.prettierignore b/autogpt_platform/frontend/.prettierignore index 2f038d1278..9951e9905d 100644 --- a/autogpt_platform/frontend/.prettierignore +++ b/autogpt_platform/frontend/.prettierignore @@ -1,5 +1,6 @@ node_modules pnpm-lock.yaml .next +.auth build public diff --git a/autogpt_platform/frontend/.storybook/main.ts b/autogpt_platform/frontend/.storybook/main.ts index fce703bc98..41553c196d 100644 --- a/autogpt_platform/frontend/.storybook/main.ts +++ b/autogpt_platform/frontend/.storybook/main.ts @@ -1,13 +1,16 @@ import type { StorybookConfig } from "@storybook/nextjs"; const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: [ + "../src/components/overview.stories.@(js|jsx|mjs|ts|tsx)", + "../src/components/tokens/**/*.stories.@(js|jsx|mjs|ts|tsx)", + "../src/components/atoms/**/*.stories.@(js|jsx|mjs|ts|tsx)", + "../src/components/agptui/**/*.stories.@(js|jsx|mjs|ts|tsx)", + ], addons: [ "@storybook/addon-a11y", "@storybook/addon-onboarding", "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", "@storybook/addon-docs", ], features: { diff --git a/autogpt_platform/frontend/.storybook/manager.ts b/autogpt_platform/frontend/.storybook/manager.ts index 20734b9f3e..7c7021cb9f 100644 --- a/autogpt_platform/frontend/.storybook/manager.ts +++ b/autogpt_platform/frontend/.storybook/manager.ts @@ -1,4 +1,4 @@ -import { addons } from "@storybook/manager-api"; +import { addons } from "storybook/manager-api"; import { theme } from "./theme"; diff --git a/autogpt_platform/frontend/.storybook/preview.tsx b/autogpt_platform/frontend/.storybook/preview.tsx index a515bbfef9..d8c67a0a3f 100644 --- a/autogpt_platform/frontend/.storybook/preview.tsx +++ b/autogpt_platform/frontend/.storybook/preview.tsx @@ -1,17 +1,16 @@ -import React from "react"; -import type { Preview } from "@storybook/react"; -import { initialize, mswLoader } from "msw-storybook-addon"; -import "../src/app/globals.css"; -import "../src/components/styles/fonts.css"; import { Controls, - Description, Primary, Source, Stories, Subtitle, Title, -} from "@storybook/blocks"; +} from "@storybook/addon-docs/blocks"; +import { Preview } from "@storybook/nextjs"; +import { initialize, mswLoader } from "msw-storybook-addon"; +import React from "react"; +import "../src/app/globals.css"; +import "../src/components/styles/fonts.css"; import { theme } from "./theme"; // Initialize MSW @@ -28,7 +27,7 @@ const preview: Preview = { <> <Subtitle /> - <Description /> + <Primary /> <Source /> <Stories /> diff --git a/autogpt_platform/frontend/.storybook/test-runner.ts b/autogpt_platform/frontend/.storybook/test-runner.ts deleted file mode 100644 index fc12dcc48d..0000000000 --- a/autogpt_platform/frontend/.storybook/test-runner.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { TestRunnerConfig } from "@storybook/test-runner"; -import { injectAxe, checkA11y } from "axe-playwright"; - -/* - * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api - * to learn more about the test-runner hooks API. - */ -const config: TestRunnerConfig = { - async preVisit(page) { - await injectAxe(page); - }, - async postVisit(page) { - await checkA11y(page, "#storybook-root", { - detailedReport: true, - detailedReportOptions: { - html: true, - }, - }); - }, -}; - -export default config; diff --git a/autogpt_platform/frontend/.storybook/theme.ts b/autogpt_platform/frontend/.storybook/theme.ts index 0a7693ddcd..bebc5b939d 100644 --- a/autogpt_platform/frontend/.storybook/theme.ts +++ b/autogpt_platform/frontend/.storybook/theme.ts @@ -1,4 +1,4 @@ -import { create } from "@storybook/theming/create"; +import { create } from "storybook/theming/create"; export const theme = create({ base: "light", diff --git a/autogpt_platform/frontend/README.md b/autogpt_platform/frontend/README.md index ee9dc60df8..18fda747d4 100644 --- a/autogpt_platform/frontend/README.md +++ b/autogpt_platform/frontend/README.md @@ -156,3 +156,9 @@ By integrating Storybook into our development workflow, we can streamline UI dev - [**Zod**](https://zod.dev/) - TypeScript-first schema validation - [**React Table**](https://tanstack.com/table) - Headless table library - [**React Flow**](https://reactflow.dev/) - Interactive node-based diagrams +- [**React Query**](https://tanstack.com/query/latest/docs/framework/react/overview) - Data fetching and caching +- [**React Query DevTools**](https://tanstack.com/query/latest/docs/framework/react/devtools) - Debugging tool for React Query + +### Development Tools + +- `NEXT_PUBLIC_REACT_QUERY_DEVTOOL` - Enable React Query DevTools. Set to `true` to enable. diff --git a/autogpt_platform/frontend/orval.config.ts b/autogpt_platform/frontend/orval.config.ts new file mode 100644 index 0000000000..afce23a441 --- /dev/null +++ b/autogpt_platform/frontend/orval.config.ts @@ -0,0 +1,59 @@ +import { defineConfig } from "orval"; + +export default defineConfig({ + autogpt_api_client: { + input: { + target: `./src/api/openapi.json`, + override: { + transformer: "./src/api/transformers/fix-tags.mjs", + }, + }, + output: { + workspace: "./src/api", + target: `./__generated__/endpoints`, + schemas: "./__generated__/models", + mode: "tags-split", + client: "react-query", + httpClient: "fetch", + indexFiles: false, + mock: { + type: "msw", + delay: 1000, // artifical latency + generateEachHttpStatus: true, // helps us test error-handling scenarios and generate mocks for all HTTP statuses + }, + override: { + mutator: { + path: "./mutators/custom-mutator.ts", + name: "customMutator", + }, + query: { + useQuery: true, + useMutation: true, + // Will add more as their use cases arise + }, + }, + }, + hooks: { + afterAllFilesWrite: "prettier --write", + }, + }, + autogpt_zod_schema: { + input: { + target: `./src/api/openapi.json`, + override: { + transformer: "./src/api/transformers/fix-tags.mjs", + }, + }, + output: { + workspace: "./src/api", + target: `./__generated__/zod-schema`, + schemas: "./__generated__/models", + mode: "tags-split", + client: "zod", + indexFiles: false, + }, + hooks: { + afterAllFilesWrite: "prettier --write", + }, + }, +}); diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json index ff174e390c..39edca7d05 100644 --- a/autogpt_platform/frontend/package.json +++ b/autogpt_platform/frontend/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "next dev --turbo", "dev:test": "NODE_ENV=test && next dev --turbo", - "build": "SKIP_STORYBOOK_TESTS=true next build", + "build": "pnpm run generate:api-client && SKIP_STORYBOOK_TESTS=true next build", "start": "next start", "start:standalone": "cd .next/standalone && node server.js", "lint": "next lint && prettier --check .", @@ -18,7 +18,10 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "test-storybook": "test-storybook", - "test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm run build-storybook -- --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && pnpm run test-storybook\"" + "test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm run build-storybook -- --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && pnpm run test-storybook\"", + "fetch:openapi": "curl http://localhost:8006/openapi.json > ./src/api/openapi.json && prettier --write ./src/api/openapi.json", + "generate:api-client": "orval --config ./orval.config.ts", + "generate:api-all": "pnpm run fetch:openapi && pnpm run generate:api-client" }, "browserslist": [ "defaults" @@ -27,6 +30,7 @@ "@faker-js/faker": "9.8.0", "@hookform/resolvers": "5.1.1", "@next/third-parties": "15.3.3", + "@phosphor-icons/react": "2.1.10", "@radix-ui/react-alert-dialog": "1.1.14", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.2", @@ -49,6 +53,7 @@ "@sentry/nextjs": "9.27.0", "@supabase/ssr": "0.6.1", "@supabase/supabase-js": "2.50.0", + "@tanstack/react-query": "5.80.7", "@tanstack/react-table": "8.21.3", "@types/jaro-winkler": "0.2.4", "@xyflow/react": "12.6.4", @@ -89,23 +94,17 @@ "zod": "3.25.56" }, "devDependencies": { - "@chromatic-com/storybook": "3.2.6", - "@playwright/test": "1.52.0", - "@storybook/addon-a11y": "8.6.14", - "@storybook/addon-docs": "8.6.14", - "@storybook/addon-essentials": "8.6.14", - "@storybook/addon-interactions": "8.6.14", - "@storybook/addon-links": "8.6.14", - "@storybook/addon-onboarding": "8.6.14", - "@storybook/blocks": "8.6.14", - "@storybook/manager-api": "8.6.14", - "@storybook/nextjs": "8.6.14", - "@storybook/react": "8.6.14", - "@storybook/test": "8.6.14", - "@storybook/test-runner": "0.22.1", - "@storybook/theming": "8.6.14", + "@chromatic-com/storybook": "4.0.0", + "@playwright/test": "1.53.1", + "@storybook/addon-a11y": "9.0.12", + "@storybook/addon-docs": "9.0.12", + "@storybook/addon-links": "9.0.12", + "@storybook/addon-onboarding": "9.0.12", + "@storybook/nextjs": "9.0.12", + "@tanstack/eslint-plugin-query": "5.78.0", + "@tanstack/react-query-devtools": "5.80.10", "@types/canvas-confetti": "1.9.0", - "@types/lodash": "4.17.17", + "@types/lodash": "4.17.18", "@types/negotiator": "0.6.4", "@types/node": "22.15.30", "@types/react": "18.3.17", @@ -115,16 +114,17 @@ "chromatic": "11.25.2", "concurrently": "9.1.2", "eslint": "8.57.1", - "eslint-config-next": "15.3.3", - "eslint-plugin-storybook": "0.12.0", - "import-in-the-middle": "1.14.0", + "eslint-config-next": "15.3.4", + "eslint-plugin-storybook": "9.0.12", + "import-in-the-middle": "1.14.2", "msw": "2.10.2", "msw-storybook-addon": "2.0.5", - "postcss": "8.5.4", + "orval": "7.10.0", + "postcss": "8.5.6", "prettier": "3.5.3", "prettier-plugin-tailwindcss": "0.6.12", "require-in-the-middle": "7.5.2", - "storybook": "8.6.14", + "storybook": "9.0.12", "tailwindcss": "3.4.17", "typescript": "5.8.3" }, diff --git a/autogpt_platform/frontend/pnpm-lock.yaml b/autogpt_platform/frontend/pnpm-lock.yaml index a258b772eb..b3d1e7975d 100644 --- a/autogpt_platform/frontend/pnpm-lock.yaml +++ b/autogpt_platform/frontend/pnpm-lock.yaml @@ -16,7 +16,10 @@ importers: version: 5.1.1(react-hook-form@7.57.0(react@18.3.1)) '@next/third-parties': specifier: 15.3.3 - version: 15.3.3(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 15.3.3(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@phosphor-icons/react': + specifier: 2.1.10 + version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-alert-dialog': specifier: 1.1.14 version: 1.1.14(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -76,13 +79,16 @@ importers: version: 1.2.7(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: 9.27.0 - version: 9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + version: 9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.99.9(esbuild@0.25.5)) '@supabase/ssr': specifier: 0.6.1 version: 0.6.1(@supabase/supabase-js@2.50.0) '@supabase/supabase-js': specifier: 2.50.0 version: 2.50.0 + '@tanstack/react-query': + specifier: 5.80.7 + version: 5.80.7(react@18.3.1) '@tanstack/react-table': specifier: 8.21.3 version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -91,7 +97,7 @@ importers: version: 0.2.4 '@xyflow/react': specifier: 12.6.4 - version: 12.6.4(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 12.6.4(@types/react@18.3.17)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ajv: specifier: 8.17.1 version: 8.17.1 @@ -127,7 +133,7 @@ importers: version: 12.16.0(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) geist: specifier: 1.4.2 - version: 1.4.2(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 1.4.2(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) jaro-winkler: specifier: 0.2.8 version: 0.2.8 @@ -145,7 +151,7 @@ importers: version: 2.30.1 next: specifier: 15.3.3 - version: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -199,56 +205,38 @@ importers: version: 3.25.56 devDependencies: '@chromatic-com/storybook': - specifier: 3.2.6 - version: 3.2.6(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) + specifier: 4.0.0 + version: 4.0.0(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@playwright/test': - specifier: 1.52.0 - version: 1.52.0 + specifier: 1.53.1 + version: 1.53.1 '@storybook/addon-a11y': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) + specifier: 9.0.12 + version: 9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@storybook/addon-docs': - specifier: 8.6.14 - version: 8.6.14(@types/react@18.3.17)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-essentials': - specifier: 8.6.14 - version: 8.6.14(@types/react@18.3.17)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-interactions': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) + specifier: 9.0.12 + version: 9.0.12(@types/react@18.3.17)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@storybook/addon-links': - specifier: 8.6.14 - version: 8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) + specifier: 9.0.12 + version: 9.0.12(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@storybook/addon-onboarding': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/blocks': - specifier: 8.6.14 - version: 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/manager-api': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) + specifier: 9.0.12 + version: 9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@storybook/nextjs': - specifier: 8.6.14 - version: 8.6.14(@swc/core@1.11.31)(esbuild@0.24.2)(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(type-fest@4.41.0)(typescript@5.8.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - '@storybook/react': - specifier: 8.6.14 - version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3) - '@storybook/test': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/test-runner': - specifier: 0.22.1 - version: 0.22.1(@types/node@22.15.30)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/theming': - specifier: 8.6.14 - version: 8.6.14(storybook@8.6.14(prettier@3.5.3)) + specifier: 9.0.12 + version: 9.0.12(esbuild@0.25.5)(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(type-fest@4.41.0)(typescript@5.8.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(esbuild@0.25.5)) + '@tanstack/eslint-plugin-query': + specifier: 5.78.0 + version: 5.78.0(eslint@8.57.1)(typescript@5.8.3) + '@tanstack/react-query-devtools': + specifier: 5.80.10 + version: 5.80.10(@tanstack/react-query@5.80.7(react@18.3.1))(react@18.3.1) '@types/canvas-confetti': specifier: 1.9.0 version: 1.9.0 '@types/lodash': - specifier: 4.17.17 - version: 4.17.17 + specifier: 4.17.18 + version: 4.17.18 '@types/negotiator': specifier: 0.6.4 version: 0.6.4 @@ -266,7 +254,7 @@ importers: version: 3.16.3 axe-playwright: specifier: 2.1.0 - version: 2.1.0(playwright@1.52.0) + version: 2.1.0(playwright@1.53.1) chromatic: specifier: 11.25.2 version: 11.25.2 @@ -277,23 +265,26 @@ importers: specifier: 8.57.1 version: 8.57.1 eslint-config-next: - specifier: 15.3.3 - version: 15.3.3(eslint@8.57.1)(typescript@5.8.3) + specifier: 15.3.4 + version: 15.3.4(eslint@8.57.1)(typescript@5.8.3) eslint-plugin-storybook: - specifier: 0.12.0 - version: 0.12.0(eslint@8.57.1)(typescript@5.8.3) + specifier: 9.0.12 + version: 9.0.12(eslint@8.57.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3) import-in-the-middle: - specifier: 1.14.0 - version: 1.14.0 + specifier: 1.14.2 + version: 1.14.2 msw: specifier: 2.10.2 version: 2.10.2(@types/node@22.15.30)(typescript@5.8.3) msw-storybook-addon: specifier: 2.0.5 version: 2.0.5(msw@2.10.2(@types/node@22.15.30)(typescript@5.8.3)) + orval: + specifier: 7.10.0 + version: 7.10.0(openapi-types@12.1.3) postcss: - specifier: 8.5.4 - version: 8.5.4 + specifier: 8.5.6 + version: 8.5.6 prettier: specifier: 3.5.3 version: 3.5.3 @@ -304,8 +295,8 @@ importers: specifier: 7.5.2 version: 7.5.2 storybook: - specifier: 8.6.14 - version: 8.6.14(prettier@3.5.3) + specifier: 9.0.12 + version: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) tailwindcss: specifier: 3.4.17 version: 3.4.17 @@ -326,6 +317,25 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@apidevtools/json-schema-ref-parser@11.7.2': + resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==} + engines: {node: '>= 16'} + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@10.1.1': + resolution: {integrity: sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==} + peerDependencies: + openapi-types: '>=7' + + '@asyncapi/specs@6.8.1': + resolution: {integrity: sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -466,27 +476,11 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-bigint@7.8.3': resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-dynamic-import@7.8.3': resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: @@ -504,64 +498,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} engines: {node: '>=6.9.0'} @@ -949,9 +891,6 @@ packages: resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@bundled-es-modules/cookie@2.0.1': resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} @@ -961,11 +900,11 @@ packages: '@bundled-es-modules/tough-cookie@0.1.6': resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - '@chromatic-com/storybook@3.2.6': - resolution: {integrity: sha512-FDmn5Ry2DzQdik+eq2sp/kJMMT36Ewe7ONXUXM2Izd97c7r6R/QyGli8eyh/F0iyqVvbLveNYFyF0dBOJNwLqw==} - engines: {node: '>=16.0.0', yarn: '>=1.22.18'} + '@chromatic-com/storybook@4.0.0': + resolution: {integrity: sha512-FfyMHK/lz/dHezWxwNZv4ReFORWVvv+bJx71NT2BSfLhOKOaoZnKJOe4QLyGxWAB7ynnedrM9V9qea3FPFj+rQ==} + engines: {node: '>=20.0.0', yarn: '>=1.22.18'} peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + storybook: ^0.0.0-0 || ^9.0.0 || ^9.1.0-0 '@date-fns/tz@1.2.0': resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} @@ -988,152 +927,152 @@ packages: '@emotion/unitless@0.8.1': resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} - '@esbuild/aix-ppc64@0.24.2': - resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.24.2': - resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.24.2': - resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.24.2': - resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.24.2': - resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.24.2': - resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.24.2': - resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.2': - resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.24.2': - resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.24.2': - resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.24.2': - resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.24.2': - resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.24.2': - resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.24.2': - resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.24.2': - resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.24.2': - resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.24.2': - resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.24.2': - resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.2': - resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.24.2': - resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.2': - resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.24.2': - resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.24.2': - resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.24.2': - resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.24.2': - resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1156,6 +1095,9 @@ packages: resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@exodus/schemasafe@1.3.0': + resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@faker-js/faker@9.8.0': resolution: {integrity: sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} @@ -1175,11 +1117,8 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@gerrit0/mini-shiki@3.6.0': + resolution: {integrity: sha512-KaeJvPNofTEZR9EzVNp/GQzbQqkGfjiu6k3CXKvhVTX+8OoAKSX/k7qxLKOX3B0yh2XqVAc93rsOu48CGt2Qug==} '@hookform/resolvers@5.1.1': resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} @@ -1199,11 +1138,13 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] + '@ibm-cloud/openapi-ruleset-utilities@1.9.0': + resolution: {integrity: sha512-AoFbSarOqFBYH+1TZ9Ahkm2IWYSi5v0pBk88fpV+5b3qGJukypX8PwvCWADjuyIccKg48/F73a6hTTkBzDQ2UA==} + engines: {node: '>=16.0.0'} + + '@ibm-cloud/openapi-ruleset@1.31.1': + resolution: {integrity: sha512-3WK2FREmDA2aadCjD71PE7tx5evyvmhg80ts1kXp2IzXIA0ZJ7guGM66tj40kxaqwpMSGchwEnnfYswntav76g==} + engines: {node: '>=16.0.0'} '@img/sharp-darwin-arm64@0.34.2': resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} @@ -1211,53 +1152,27 @@ packages: cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - '@img/sharp-darwin-x64@0.34.2': resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.1.0': resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-darwin-x64@1.1.0': resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linux-arm64@1.1.0': resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - '@img/sharp-libvips-linux-arm@1.1.0': resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] @@ -1268,123 +1183,62 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - '@img/sharp-libvips-linux-s390x@1.1.0': resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linux-x64@1.1.0': resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.1.0': resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linux-arm64@0.34.2': resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - '@img/sharp-linux-arm@0.34.2': resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - '@img/sharp-linux-s390x@0.34.2': resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linux-x64@0.34.2': resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.2': resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - '@img/sharp-linuxmusl-x64@0.34.2': resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - '@img/sharp-wasm32@0.34.2': resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1396,24 +1250,12 @@ packages: cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - '@img/sharp-win32-ia32@0.34.2': resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@img/sharp-win32-x64@0.34.2': resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1455,84 +1297,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/create-cache-key-function@29.7.0': - resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -1554,6 +1318,27 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + + '@jsep-plugin/assignment@1.3.0': + resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@jsep-plugin/regex@1.0.4': + resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + + '@jsep-plugin/ternary@1.1.4': + resolution: {integrity: sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg==} + engines: {node: '>= 10.16.0'} + peerDependencies: + jsep: ^0.4.0||^1.0.0 + '@mdx-js/react@3.1.0': resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} peerDependencies: @@ -1564,14 +1349,17 @@ packages: resolution: {integrity: sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@0.2.10': - resolution: {integrity: sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==} + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@neoconfetti/react@1.0.0': + resolution: {integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==} '@next/env@15.3.3': resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} - '@next/eslint-plugin-next@15.3.3': - resolution: {integrity: sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q==} + '@next/eslint-plugin-next@15.3.4': + resolution: {integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==} '@next/swc-darwin-arm64@15.3.3': resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==} @@ -1840,12 +1628,49 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@orval/angular@7.10.0': + resolution: {integrity: sha512-M89GKo/PibxYXvOKp9+i6BLxhEW8YsO+evwuV2kMbDGNS3RiYDwzmMBcA9SVL7m8CumeZoxNEAXsupzq96ZAXA==} + + '@orval/axios@7.10.0': + resolution: {integrity: sha512-AB6BjEwyguIcH8olzOTFPvwUP8z63yP4Jfl3T2UoeFchK04KqWqxbUoxmDG9xVQ79uMs/uOrb0X+GFwdZ56gAg==} + + '@orval/core@7.10.0': + resolution: {integrity: sha512-Lm7HY4Kwzehe+2HNfi+Ov/IZ+m3nj3NskVGvOyJDAqaaHB7G/xydSCtgELG32ur4G+M/XmwChAjoP4TCNVh0VA==} + + '@orval/fetch@7.10.0': + resolution: {integrity: sha512-bWcXPmARcXhXRveBtUnkfPlkUcLEzfGaflAdqN4CtScS48LgNrXXtuyt2BV2wvEXAavCWIhnRyQvz2foTU4U8Q==} + + '@orval/hono@7.10.0': + resolution: {integrity: sha512-bOxTdZxx2BpGQf7fFuCeeUe//ZYDWc6Yz9WOhj3HrnsD06xTRKFWVBi/QZ29QcAPxqwunu/VWwbqoiHHuuX3bA==} + + '@orval/mcp@7.10.0': + resolution: {integrity: sha512-ztLXGOSxK7jFwPKAeYPR85BjKRh3KTClKEnM2MFmo2FHHojn72DPXRPCmy0Wbw5Ee+JOxK2kIpyx+HZi9XVxiA==} + + '@orval/mock@7.10.0': + resolution: {integrity: sha512-vkEWCaKEyMfWGJF5MtxVzl+blwc9vYzwdYxMoSdjA5yS2dNBrdNlt1aLtb4+aoI1jgBgpCg/OB7VtWaL5QYidA==} + + '@orval/query@7.10.0': + resolution: {integrity: sha512-DBVg8RyKWSQKhr5Zfvxx5XICUdDUkG4MJKSd4BQCrRjUWgN6vwGunMEKyfnjpS5mFUSCkwWD/I3rTkjW6aysJA==} + + '@orval/swr@7.10.0': + resolution: {integrity: sha512-ZdApomZQhJ5ZogjJgBK+haeCOP9gUaMaGKGjTVJr86jJaygDcKn54Ok1quiDUCbX42Eye+cgmQJeKeZvqnPohA==} + + '@orval/zod@7.10.0': + resolution: {integrity: sha512-AB/508IBMlVDBcGvlq+ASz7DvqU3nhoDnIeBCyjwNfQwhYzREU0qqiFBnH0XAW70c6SCMf9/bIcYbw8GAx/zxA==} + + '@phosphor-icons/react@2.1.10': + resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} + engines: {node: '>=10'} + peerDependencies: + react: '>= 16.8' + react-dom: '>= 16.8' + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.52.0': - resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==} + '@playwright/test@1.53.1': + resolution: {integrity: sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==} engines: {node: '>=18'} hasBin: true @@ -2630,147 +2455,139 @@ packages: peerDependencies: webpack: '>=4.40.0' - '@sideway/address@4.1.5': - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + '@shikijs/engine-oniguruma@3.6.0': + resolution: {integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==} - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + '@shikijs/langs@3.6.0': + resolution: {integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==} - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@shikijs/themes@3.6.0': + resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@shikijs/types@3.6.0': + resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==} - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@storybook/addon-a11y@8.6.14': - resolution: {integrity: sha512-fozv6enO9IgpWq2U8qqS8MZ21Nt+MVHiRQe3CjnCpBOejTyo/ATm690PeYYRVHVG6M/15TVePb0h3ngKQbrrzQ==} + '@stoplight/better-ajv-errors@1.0.3': + resolution: {integrity: sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==} + engines: {node: ^12.20 || >= 14.13} peerDependencies: - storybook: ^8.6.14 + ajv: '>=8' - '@storybook/addon-actions@8.6.14': - resolution: {integrity: sha512-mDQxylxGGCQSK7tJPkD144J8jWh9IU9ziJMHfB84PKpI/V5ZgqMDnpr2bssTrUaGDqU5e1/z8KcRF+Melhs9pQ==} + '@stoplight/json-ref-readers@1.2.2': + resolution: {integrity: sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ==} + engines: {node: '>=8.3.0'} + + '@stoplight/json-ref-resolver@3.1.6': + resolution: {integrity: sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A==} + engines: {node: '>=8.3.0'} + + '@stoplight/json@3.21.7': + resolution: {integrity: sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==} + engines: {node: '>=8.3.0'} + + '@stoplight/ordered-object-literal@1.0.5': + resolution: {integrity: sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==} + engines: {node: '>=8'} + + '@stoplight/path@1.3.2': + resolution: {integrity: sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==} + engines: {node: '>=8'} + + '@stoplight/spectral-core@1.20.0': + resolution: {integrity: sha512-5hBP81nCC1zn1hJXL/uxPNRKNcB+/pEIHgCjPRpl/w/qy9yC9ver04tw1W0l/PMiv0UeB5dYgozXVQ4j5a6QQQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-formats@1.8.2': + resolution: {integrity: sha512-c06HB+rOKfe7tuxg0IdKDEA5XnjL2vrn/m/OVIIxtINtBzphZrOgtRn7epQ5bQF5SWp84Ue7UJWaGgDwVngMFw==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-functions@1.10.1': + resolution: {integrity: sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-parsers@1.0.5': + resolution: {integrity: sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-ref-resolver@1.0.5': + resolution: {integrity: sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-rulesets@1.22.0': + resolution: {integrity: sha512-l2EY2jiKKLsvnPfGy+pXC0LeGsbJzcQP5G/AojHgf+cwN//VYxW1Wvv4WKFx/CLmLxc42mJYF2juwWofjWYNIQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/spectral-runtime@1.1.4': + resolution: {integrity: sha512-YHbhX3dqW0do6DhiPSgSGQzr6yQLlWybhKwWx0cqxjMwxej3TqLv3BXMfIUYFKKUqIwH4Q2mV8rrMM8qD2N0rQ==} + engines: {node: ^16.20 || ^18.18 || >= 20.17} + + '@stoplight/types@13.20.0': + resolution: {integrity: sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==} + engines: {node: ^12.20 || >=14.13} + + '@stoplight/types@13.6.0': + resolution: {integrity: sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==} + engines: {node: ^12.20 || >=14.13} + + '@stoplight/types@14.1.1': + resolution: {integrity: sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==} + engines: {node: ^12.20 || >=14.13} + + '@stoplight/yaml-ast-parser@0.0.50': + resolution: {integrity: sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==} + + '@stoplight/yaml@4.3.0': + resolution: {integrity: sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==} + engines: {node: '>=10.8'} + + '@storybook/addon-a11y@9.0.12': + resolution: {integrity: sha512-xdJPYNxYU6A3DA48h6y0o3XziCp4YDGXcFKkc5Ce1GPFCa7ebFFh2trHqzevoFSGdQxWc5M3W0A4dhQtkpT4Ww==} peerDependencies: - storybook: ^8.6.14 + storybook: ^9.0.12 - '@storybook/addon-backgrounds@8.6.14': - resolution: {integrity: sha512-l9xS8qWe5n4tvMwth09QxH2PmJbCctEvBAc1tjjRasAfrd69f7/uFK4WhwJAstzBTNgTc8VXI4w8ZR97i1sFbg==} + '@storybook/addon-docs@9.0.12': + resolution: {integrity: sha512-bAuFy4BWGEBIC0EAS4N+V8mHj7NZiSdDnJUSr4Al3znEVzNHLpQAMRznkga2Ok8x+gwcyBG7W47dLbDXVqLZDg==} peerDependencies: - storybook: ^8.6.14 + storybook: ^9.0.12 - '@storybook/addon-controls@8.6.14': - resolution: {integrity: sha512-IiQpkNJdiRyA4Mq9mzjZlvQugL/aE7hNgVxBBGPiIZG6wb6Ht9hNnBYpap5ZXXFKV9p2qVI0FZK445ONmAa+Cw==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-docs@8.6.14': - resolution: {integrity: sha512-Obpd0OhAF99JyU5pp5ci17YmpcQtMNgqW2pTXV8jAiiipWpwO++hNDeQmLmlSXB399XjtRDOcDVkoc7rc6JzdQ==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-essentials@8.6.14': - resolution: {integrity: sha512-5ZZSHNaW9mXMOFkoPyc3QkoNGdJHETZydI62/OASR0lmPlJ1065TNigEo5dJddmZNn0/3bkE8eKMAzLnO5eIdA==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-highlight@8.6.14': - resolution: {integrity: sha512-4H19OJlapkofiE9tM6K/vsepf4ir9jMm9T+zw5L85blJZxhKZIbJ6FO0TCG9PDc4iPt3L6+aq5B0X29s9zicNQ==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-interactions@8.6.14': - resolution: {integrity: sha512-8VmElhm2XOjh22l/dO4UmXxNOolGhNiSpBcls2pqWSraVh4a670EyYBZsHpkXqfNHo2YgKyZN3C91+9zfH79qQ==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-links@8.6.14': - resolution: {integrity: sha512-DRlXHIyZzOruAZkxmXfVgTF+4d6K27pFcH4cUsm3KT1AXuZbr23lb5iZHpUZoG6lmU85Sru4xCEgewSTXBIe1w==} + '@storybook/addon-links@9.0.12': + resolution: {integrity: sha512-Ix7WXnCMkEWrgBrGCoZAFH276Xj/4g8e5Kao9BSZneUBjJJC2LuCaTftGeiM4kQ7sKBPOc/L6zIyusWckBvXIQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 + storybook: ^9.0.12 peerDependenciesMeta: react: optional: true - '@storybook/addon-measure@8.6.14': - resolution: {integrity: sha512-1Tlyb72NX8aAqm6I6OICsUuGOP6hgnXcuFlXucyhKomPa6j3Eu2vKu561t/f0oGtAK2nO93Z70kVaEh5X+vaGw==} + '@storybook/addon-onboarding@9.0.12': + resolution: {integrity: sha512-hqgaINYMDiA2op+Cb77LvwdJkgpMUMAnp5ugJjkn5icLpSTkZxnaQrlC0lTHOZBxUjR5NlS2ApSAuMvrCXQLAw==} peerDependencies: - storybook: ^8.6.14 + storybook: ^9.0.12 - '@storybook/addon-onboarding@8.6.14': - resolution: {integrity: sha512-bHdHiGJFigVcSzMIsNLHY5IODZHr+nKwyz5/QOZLMkLcGH2IaUbOJfm4RyGOaTTPsUtAKbdsVXNEG3Otf+qO9A==} + '@storybook/builder-webpack5@9.0.12': + resolution: {integrity: sha512-zfmGYZjDppYPZZgSwW9ZRfIrCvshZcLombKmVEodlt/RMs5N5zaTCNf5p7+Z1BBcRpH5HXHWjwmrlobW6LzsLg==} peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-outline@8.6.14': - resolution: {integrity: sha512-CW857JvN6OxGWElqjlzJO2S69DHf+xO3WsEfT5mT3ZtIjmsvRDukdWfDU9bIYUFyA2lFvYjncBGjbK+I91XR7w==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-toolbars@8.6.14': - resolution: {integrity: sha512-W/wEXT8h3VyZTVfWK/84BAcjAxTdtRiAkT2KAN0nbSHxxB5KEM1MjKpKu2upyzzMa3EywITqbfy4dP6lpkVTwQ==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/addon-viewport@8.6.14': - resolution: {integrity: sha512-gNzVQbMqRC+/4uQTPI2ZrWuRHGquTMZpdgB9DrD88VTEjNudP+J6r8myLfr2VvGksBbUMHkGHMXHuIhrBEnXYA==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/blocks@8.6.14': - resolution: {integrity: sha512-rBMHAfA39AGHgkrDze4RmsnQTMw1ND5fGWobr9pDcJdnDKWQWNRD7Nrlxj0gFlN3n4D9lEZhWGdFrCbku7FVAQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^8.6.14 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - - '@storybook/builder-webpack5@8.6.14': - resolution: {integrity: sha512-YZYAqc6NBKoMTKZpjxnkMch6zDtMkBZdS/yaji1+wJX2QPFBwTbSh7SpeBxDp1S11gXSAJ4f1btUWeqSqo8nJA==} - peerDependencies: - storybook: ^8.6.14 + storybook: ^9.0.12 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@storybook/components@8.6.14': - resolution: {integrity: sha512-HNR2mC5I4Z5ek8kTrVZlIY/B8gJGs5b3XdZPBPBopTIN6U/YHXiDyOjY3JlaS4fSG1fVhp/Qp1TpMn1w/9m1pw==} + '@storybook/core-webpack@9.0.12': + resolution: {integrity: sha512-soFgpQh8pfba94YjkFBMNO4460/NEhdWe2WUPJs5drz2tOyGSElYma9KKjkbn+SQtr4qG0Xu7P56e8DJMXiMDg==} peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + storybook: ^9.0.12 - '@storybook/core-webpack@8.6.14': - resolution: {integrity: sha512-iG7r8osNKabSGBbuJuSeMWKbU+ilt5PvzTYkClcYaagla/DliXkXvfywA6jOugVk/Cpx+c6tVKlPfjLcaQHwmw==} + '@storybook/csf-plugin@9.0.12': + resolution: {integrity: sha512-5EueJQJAu77Lh+EedG4Q/kEOZNlTY/g+fWsT7B5DTtLVy0ypnghsHY8X3KYT/0+NNgTtoO0if4F+ejVYaLnMzA==} peerDependencies: - storybook: ^8.6.14 - - '@storybook/core@8.6.14': - resolution: {integrity: sha512-1P/w4FSNRqP8j3JQBOi3yGt8PVOgSRbP66Ok520T78eJBeqx9ukCfl912PQZ7SPbW3TIunBwLXMZOjZwBB/JmA==} - peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - - '@storybook/csf-plugin@8.6.14': - resolution: {integrity: sha512-dErtc9teAuN+eelN8FojzFE635xlq9cNGGGEu0WEmMUQ4iJ8pingvBO1N8X3scz4Ry7KnxX++NNf3J3gpxS8qQ==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/csf@0.1.13': - resolution: {integrity: sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==} + storybook: ^9.0.12 '@storybook/global@5.0.0': resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} @@ -2782,24 +2599,14 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - '@storybook/instrumenter@8.6.14': - resolution: {integrity: sha512-iG4MlWCcz1L7Yu8AwgsnfVAmMbvyRSk700Mfy2g4c8y5O+Cv1ejshE1LBBsCwHgkuqU0H4R0qu4g23+6UnUemQ==} + '@storybook/nextjs@9.0.12': + resolution: {integrity: sha512-7+toUNcn17S1MlJTMj3miZ3ev8WIeWcr/2OVbwzFp+BKyZVVOXd5+YSWBqwg+k4H5CmwTrFYegQ3Yp2lqzgKoA==} + engines: {node: '>=20.0.0'} peerDependencies: - storybook: ^8.6.14 - - '@storybook/manager-api@8.6.14': - resolution: {integrity: sha512-ez0Zihuy17udLbfHZQXkGqwtep0mSGgHcNzGN7iZrMP1m+VmNo+7aGCJJdvXi7+iU3yq8weXSQFWg5DqWgLS7g==} - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - - '@storybook/nextjs@8.6.14': - resolution: {integrity: sha512-HbOOpwxJxO8nIDBvEQL3Pt51GHxnSeVxQ/WApr1HCT5Ffu6KCHz8WVsX56taHdigxjonSq0NTnog+aTIP06Nkw==} - engines: {node: '>=18.0.0'} - peerDependencies: - next: ^13.5.0 || ^14.0.0 || ^15.0.0 + next: ^14.1.0 || ^15.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 + storybook: ^9.0.12 typescript: '*' webpack: ^5.0.0 peerDependenciesMeta: @@ -2808,68 +2615,43 @@ packages: webpack: optional: true - '@storybook/preset-react-webpack@8.6.14': - resolution: {integrity: sha512-M7Q6ErNx7N2hQorTz0OLa3YV8nc8OcvkDlCxqqnkHPGQNEIWEpeDvq3wn2OvZlrHDpchyuiquGXZ8aztVtBP2g==} - engines: {node: '>=18.0.0'} + '@storybook/preset-react-webpack@9.0.12': + resolution: {integrity: sha512-Tl3Qrll29dwltI4lP15zbeSXqikKa4Ucp6NKf4+W/4adzOqGRjj/bIzO3FsIPoGNs4wfy5DsOgUUPHxI4Jx97w==} + engines: {node: '>=20.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 + storybook: ^9.0.12 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@storybook/preview-api@8.6.14': - resolution: {integrity: sha512-2GhcCd4dNMrnD7eooEfvbfL4I83qAqEyO0CO7JQAmIO6Rxb9BsOLLI/GD5HkvQB73ArTJ+PT50rfaO820IExOQ==} - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0': resolution: {integrity: sha512-KUqXC3oa9JuQ0kZJLBhVdS4lOneKTOopnNBK4tUAgoxWQ3u/IjzdueZjFr7gyBrXMoU6duutk3RQR9u8ZpYJ4Q==} peerDependencies: typescript: '>= 4.x' webpack: '>= 4' - '@storybook/react-dom-shim@8.6.14': - resolution: {integrity: sha512-0hixr3dOy3f3M+HBofp3jtMQMS+sqzjKNgl7Arfuj3fvjmyXOks/yGjDImySR4imPtEllvPZfhiQNlejheaInw==} + '@storybook/react-dom-shim@9.0.12': + resolution: {integrity: sha512-OMBitzkJRga/UJF1ScSnaxgBSlAVePCK8wzPkGDn0MmsjZ4oDWuNZeKnVO1+tb6n2rZHws7RmKGxHzHAZTY+zQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 + storybook: ^9.0.12 - '@storybook/react@8.6.14': - resolution: {integrity: sha512-BOepx5bBFwl/CPI+F+LnmMmsG1wQYmrX/UQXgUbHQUU9Tj7E2ndTnNbpIuSLc8IrM03ru+DfwSg1Co3cxWtT+g==} - engines: {node: '>=18.0.0'} + '@storybook/react@9.0.12': + resolution: {integrity: sha512-rDrf5MDfsguNDTSOfGqhAjQDhp3jDMdzAoCqLjQ75M647C8nsv9i+fftO3k0rMxIJRrESpZWqVZ4tsjOX+J3DA==} + engines: {node: '>=20.0.0'} peerDependencies: - '@storybook/test': 8.6.14 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 - typescript: '>= 4.2.x' + storybook: ^9.0.12 + typescript: '>= 4.9.x' peerDependenciesMeta: - '@storybook/test': - optional: true typescript: optional: true - '@storybook/test-runner@0.22.1': - resolution: {integrity: sha512-F5omZH0Pj2Y0UXSqShl1RuPrnhLBbb/yPFnZbVWDSPWZHDSX+dfBuu1T2zVfJplNKd04RzJuMbWHPFtZ0mimSw==} - engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - storybook: ^0.0.0-0 || ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 || ^9.0.0-0 - - '@storybook/test@8.6.14': - resolution: {integrity: sha512-GkPNBbbZmz+XRdrhMtkxPotCLOQ1BaGNp/gFZYdGDk2KmUWBKmvc5JxxOhtoXM2703IzNFlQHSSNnhrDZYuLlw==} - peerDependencies: - storybook: ^8.6.14 - - '@storybook/theming@8.6.14': - resolution: {integrity: sha512-r4y+LsiB37V5hzpQo+BM10PaCsp7YlZ0YcZzQP1OCkPlYXmUAFy2VvDKaFRpD8IeNPKug2u4iFm/laDEbs03dg==} - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@supabase/auth-js@2.70.0': resolution: {integrity: sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==} @@ -2897,89 +2679,33 @@ packages: '@supabase/supabase-js@2.50.0': resolution: {integrity: sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==} - '@swc/core-darwin-arm64@1.11.31': - resolution: {integrity: sha512-NTEaYOts0OGSbJZc0O74xsji+64JrF1stmBii6D5EevWEtrY4wlZhm8SiP/qPrOB+HqtAihxWIukWkP2aSdGSQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - - '@swc/core-darwin-x64@1.11.31': - resolution: {integrity: sha512-THSGaSwT96JwXDwuXQ6yFBbn+xDMdyw7OmBpnweAWsh5DhZmQkALEm1DgdQO3+rrE99MkmzwAfclc0UmYro/OA==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-linux-arm-gnueabihf@1.11.31': - resolution: {integrity: sha512-laKtQFnW7KHgE57Hx32os2SNAogcuIDxYE+3DYIOmDMqD7/1DCfJe6Rln2N9WcOw6HuDbDpyQavIwZNfSAa8vQ==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.11.31': - resolution: {integrity: sha512-T+vGw9aPE1YVyRxRr1n7NAdkbgzBzrXCCJ95xAZc/0+WUwmL77Z+js0J5v1KKTRxw4FvrslNCOXzMWrSLdwPSA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-musl@1.11.31': - resolution: {integrity: sha512-Mztp5NZkyd5MrOAG+kl+QSn0lL4Uawd4CK4J7wm97Hs44N9DHGIG5nOz7Qve1KZo407Y25lTxi/PqzPKHo61zQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-x64-gnu@1.11.31': - resolution: {integrity: sha512-DDVE0LZcXOWwOqFU1Xi7gdtiUg3FHA0vbGb3trjWCuI1ZtDZHEQYL4M3/2FjqKZtIwASrDvO96w91okZbXhvMg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-musl@1.11.31': - resolution: {integrity: sha512-mJA1MzPPRIfaBUHZi0xJQ4vwL09MNWDeFtxXb0r4Yzpf0v5Lue9ymumcBPmw/h6TKWms+Non4+TDquAsweuKSw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-win32-arm64-msvc@1.11.31': - resolution: {integrity: sha512-RdtakUkNVAb/FFIMw3LnfNdlH1/ep6KgiPDRlmyUfd0WdIQ3OACmeBegEFNFTzi7gEuzy2Yxg4LWf4IUVk8/bg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.11.31': - resolution: {integrity: sha512-hErXdCGsg7swWdG1fossuL8542I59xV+all751mYlBoZ8kOghLSKObGQTkBbuNvc0sUKWfWg1X0iBuIhAYar+w==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-x64-msvc@1.11.31': - resolution: {integrity: sha512-5t7SGjUBMMhF9b5j17ml/f/498kiBJNf4vZFNM421UGUEETdtjPN9jZIuQrowBkoFGJTCVL/ECM4YRtTH30u/A==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core@1.11.31': - resolution: {integrity: sha512-mAby9aUnKRjMEA7v8cVZS9Ah4duoRBnX7X6r5qrhTxErx+68MoY1TPrVwj/66/SWN3Bl+jijqAqoB8Qx0QE34A==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '>=0.5.17' - peerDependenciesMeta: - '@swc/helpers': - optional: true - '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@swc/jest@0.2.38': - resolution: {integrity: sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==} - engines: {npm: '>= 7.0.0'} + '@tanstack/eslint-plugin-query@5.78.0': + resolution: {integrity: sha512-hYkhWr3UP0CkAsn/phBVR98UQawbw8CmTSgWtdgEBUjI60/GBaEIkpgi/Bp/2I8eIDK4+vdY7ac6jZx+GR+hEQ==} peerDependencies: - '@swc/core': '*' + eslint: ^8.57.0 || ^9.0.0 - '@swc/types@0.1.22': - resolution: {integrity: sha512-D13mY/ZA4PPEFSy6acki9eBT/3WgjMoRqNcdpIvjaYLQ44Xk5BdaL7UkDxAh6Z9UOe7tCCp67BVmZCojYp9owg==} + '@tanstack/query-core@5.80.7': + resolution: {integrity: sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==} + + '@tanstack/query-devtools@5.80.0': + resolution: {integrity: sha512-D6gH4asyjaoXrCOt5vG5Og/YSj0D/TxwNQgtLJIgWbhbWCC/emu2E92EFoVHh4ppVWg1qT2gKHvKyQBEFZhCuA==} + + '@tanstack/react-query-devtools@5.80.10': + resolution: {integrity: sha512-6JL63fSc7kxyGOLV2w466SxhMn/m7LZk/ximQciy6OpVt+n2A8Mq3S0QwhIzfm4WEwLK/F3OELfzRToQburnYA==} + peerDependencies: + '@tanstack/react-query': ^5.80.10 + react: ^18 || ^19 + + '@tanstack/react-query@5.80.7': + resolution: {integrity: sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==} + peerDependencies: + react: ^18 || ^19 '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} @@ -2996,12 +2722,12 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.5.0': - resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + '@testing-library/jest-dom@6.6.3': + resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - '@testing-library/user-event@14.5.2': - resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' @@ -3078,6 +2804,9 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/es-aggregate-error@1.0.6': + resolution: {integrity: sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -3090,30 +2819,15 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/jaro-winkler@0.2.4': resolution: {integrity: sha512-TNVu6vL0Z3h+hYcW78IRloINA0y0MTVJ1PFVtVpBSgk+ejmaH5aVfcVghzNXZ0fa6gXe4zapNMQtMGWOJKTLig==} @@ -3126,8 +2840,8 @@ packages: '@types/junit-report-builder@3.0.2': resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==} - '@types/lodash@4.17.17': - resolution: {integrity: sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==} + '@types/lodash@4.17.18': + resolution: {integrity: sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -3159,8 +2873,8 @@ packages: '@types/phoenix@1.6.6': resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} '@types/react-dom@18.3.5': resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} @@ -3182,9 +2896,6 @@ packages: '@types/shimmer@1.2.0': resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -3203,185 +2914,180 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - - '@types/wait-on@5.3.4': - resolution: {integrity: sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==} + '@types/urijs@1.19.25': + resolution: {integrity: sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==} '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - - '@typescript-eslint/eslint-plugin@8.33.1': - resolution: {integrity: sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==} + '@typescript-eslint/eslint-plugin@8.34.1': + resolution: {integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.33.1 + '@typescript-eslint/parser': ^8.34.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.33.1': - resolution: {integrity: sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==} + '@typescript-eslint/parser@8.34.1': + resolution: {integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.33.1': - resolution: {integrity: sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==} + '@typescript-eslint/project-service@8.34.1': + resolution: {integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.33.1': - resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==} + '@typescript-eslint/scope-manager@8.34.1': + resolution: {integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.33.1': - resolution: {integrity: sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==} + '@typescript-eslint/tsconfig-utils@8.34.1': + resolution: {integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/type-utils@8.33.1': - resolution: {integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==} + '@typescript-eslint/type-utils@8.34.1': + resolution: {integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.33.1': - resolution: {integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==} + '@typescript-eslint/types@8.34.1': + resolution: {integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.33.1': - resolution: {integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==} + '@typescript-eslint/typescript-estree@8.34.1': + resolution: {integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.33.1': - resolution: {integrity: sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==} + '@typescript-eslint/utils@8.34.1': + resolution: {integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.33.1': - resolution: {integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==} + '@typescript-eslint/visitor-keys@8.34.1': + resolution: {integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@unrs/resolver-binding-darwin-arm64@1.7.10': - resolution: {integrity: sha512-ABsM3eEiL3yu903G0uxgvGAoIw011XjTzyEk//gGtuVY1PuXP2IJG6novd6DBjm7MaWmRV/CZFY1rWBXSlSVVw==} + '@unrs/resolver-binding-android-arm-eabi@1.9.0': + resolution: {integrity: sha512-h1T2c2Di49ekF2TE8ZCoJkb+jwETKUIPDJ/nO3tJBKlLFPu+fyd93f0rGP/BvArKx2k2HlRM4kqkNarj3dvZlg==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.9.0': + resolution: {integrity: sha512-sG1NHtgXtX8owEkJ11yn34vt0Xqzi3k9TJ8zppDmyG8GZV4kVWw44FHwKwHeEFl07uKPeC4ZoyuQaGh5ruJYPA==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.9.0': + resolution: {integrity: sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==} cpu: [arm64] os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.7.10': - resolution: {integrity: sha512-lGVWy4FQEDo/PuI1VQXaQCY0XUg4xUJilf3fQ8NY4wtsQTm9lbasbUYf3nkoma+O2/do90jQTqkb02S3meyTDg==} + '@unrs/resolver-binding-darwin-x64@1.9.0': + resolution: {integrity: sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==} cpu: [x64] os: [darwin] - '@unrs/resolver-binding-freebsd-x64@1.7.10': - resolution: {integrity: sha512-g9XLCHzNGatY79JJNgxrUH6uAAfBDj2NWIlTnqQN5odwGKjyVfFZ5tFL1OxYPcxTHh384TY5lvTtF+fuEZNvBQ==} + '@unrs/resolver-binding-freebsd-x64@1.9.0': + resolution: {integrity: sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==} cpu: [x64] os: [freebsd] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.10': - resolution: {integrity: sha512-zV0ZMNy50sJFJapsjec8onyL9YREQKT88V8KwMoOA+zki/duFUP0oyTlbax1jGKdh8rQnruvW9VYkovGvdBAsw==} + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.0': + resolution: {integrity: sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.10': - resolution: {integrity: sha512-jQxgb1DIDI7goyrabh4uvyWWBrFRfF+OOnS9SbF15h52g3Qjn/u8zG7wOQ0NjtcSMftzO75TITu9aHuI7FcqQQ==} + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.0': + resolution: {integrity: sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.7.10': - resolution: {integrity: sha512-9wVVlO6+aNlm90YWitwSI++HyCyBkzYCwMi7QbuGrTxDFm2pAgtpT0OEliaI7tLS8lAWYuDbzRRCJDgsdm6nwg==} + '@unrs/resolver-binding-linux-arm64-gnu@1.9.0': + resolution: {integrity: sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-arm64-musl@1.7.10': - resolution: {integrity: sha512-FtFweORChdXOes0RAAyTZp6I4PodU2cZiSILAbGaEKDXp378UOumD2vaAkWHNxpsreQUKRxG5O1uq9EoV1NiVQ==} + '@unrs/resolver-binding-linux-arm64-musl@1.9.0': + resolution: {integrity: sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.10': - resolution: {integrity: sha512-B+hOjpG2ncCR96a9d9ww1dWVuRVC2NChD0bITgrUhEWBhpdv2o/Mu2l8MsB2fzjdV/ku+twaQhr8iLHBoZafZQ==} + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.0': + resolution: {integrity: sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==} cpu: [ppc64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.10': - resolution: {integrity: sha512-DS6jFDoQCFsnsdLXlj3z3THakQLBic63B6A0rpQ1kpkyKa3OzEfqhwRNVaywuUuOKP9bX55Jk2uqpvn/hGjKCg==} + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.0': + resolution: {integrity: sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-musl@1.7.10': - resolution: {integrity: sha512-A82SB6yEaA8EhIW2r0I7P+k5lg7zPscFnGs1Gna5rfPwoZjeUAGX76T55+DiyTiy08VFKUi79PGCulXnfjDq0g==} + '@unrs/resolver-binding-linux-riscv64-musl@1.9.0': + resolution: {integrity: sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-s390x-gnu@1.7.10': - resolution: {integrity: sha512-J+VmOPH16U69QshCp9WS+Zuiuu9GWTISKchKIhLbS/6JSCEfw2A4N02whv2VmrkXE287xxZbhW1p6xlAXNzwqg==} + '@unrs/resolver-binding-linux-s390x-gnu@1.9.0': + resolution: {integrity: sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==} cpu: [s390x] os: [linux] - '@unrs/resolver-binding-linux-x64-gnu@1.7.10': - resolution: {integrity: sha512-bYTdDltcB/V3fEqpx8YDwDw8ta9uEg8TUbJOtek6JM42u9ciJ7R/jBjNeAOs+QbyxGDd2d6xkBaGwty1HzOz3Q==} + '@unrs/resolver-binding-linux-x64-gnu@1.9.0': + resolution: {integrity: sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-linux-x64-musl@1.7.10': - resolution: {integrity: sha512-NYZ1GvSuTokJ28lqcjrMTnGMySoo4dVcNK/nsNCKCXT++1zekZtJaE+N+4jc1kR7EV0fc1OhRrOGcSt7FT9t8w==} + '@unrs/resolver-binding-linux-x64-musl@1.9.0': + resolution: {integrity: sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-wasm32-wasi@1.7.10': - resolution: {integrity: sha512-MRjJhTaQzLoX8OtzRBQDJ84OJ8IX1FqpRAUSxp/JtPeak+fyDfhXaEjcA/fhfgrACUnvC+jWC52f/V6MixSKCQ==} + '@unrs/resolver-binding-wasm32-wasi@1.9.0': + resolution: {integrity: sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@unrs/resolver-binding-win32-arm64-msvc@1.7.10': - resolution: {integrity: sha512-Cgw6qhdsfzXJnHb006CzqgaX8mD445x5FGKuueaLeH1ptCxDbzRs8wDm6VieOI7rdbstfYBaFtaYN7zBT5CUPg==} + '@unrs/resolver-binding-win32-arm64-msvc@1.9.0': + resolution: {integrity: sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==} cpu: [arm64] os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.7.10': - resolution: {integrity: sha512-Z7oECyIT2/HsrWpJ6wi2b+lVbPmWqQHuW5zeatafoRXizk1+2wUl+aSop1PF58XcyBuwPP2YpEUUpMZ8ILV4fA==} + '@unrs/resolver-binding-win32-ia32-msvc@1.9.0': + resolution: {integrity: sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==} cpu: [ia32] os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.7.10': - resolution: {integrity: sha512-DGAOo5asNvDsmFgwkb7xsgxNyN0If6XFYwDIC1QlRE7kEYWIMRChtWJyHDf30XmGovDNOs/37krxhnga/nm/4w==} + '@unrs/resolver-binding-win32-x64-msvc@1.9.0': + resolution: {integrity: sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==} cpu: [x64] os: [win32] - '@vitest/expect@2.0.5': - resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@3.0.9': + resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==} - '@vitest/pretty-format@2.0.5': - resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + '@vitest/pretty-format@3.0.9': + resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==} - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/spy@3.0.9': + resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==} - '@vitest/spy@2.0.5': - resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - - '@vitest/utils@2.0.5': - resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.0.9': + resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3457,11 +3163,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -3475,9 +3176,18 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-errors@3.0.0: + resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==} + peerDependencies: + ajv: ^8.0.1 ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -3503,14 +3213,14 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@6.2.1: - resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} - engines: {node: '>=14.16'} - ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} engines: {'0': node >= 0.8.0} @@ -3529,10 +3239,6 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -3552,19 +3258,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - append-transform@2.0.0: - resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} - engines: {node: '>=8'} - - archy@1.0.0: - resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} - arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3587,6 +3283,10 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -3628,13 +3328,14 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -3654,19 +3355,10 @@ packages: peerDependencies: playwright: '>1.0.0' - axios@1.9.0: - resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - babel-loader@9.2.1: resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} engines: {node: '>= 14.15.0'} @@ -3674,14 +3366,6 @@ packages: '@babel/core': ^7.12.0 webpack: '>=5' - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-plugin-polyfill-corejs2@0.4.13: resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} peerDependencies: @@ -3697,17 +3381,6 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-preset-current-node-syntax@1.1.0: - resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} - peerDependencies: - '@babel/core': ^7.0.0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -3740,11 +3413,11 @@ packages: boring-avatars@1.11.2: resolution: {integrity: sha512-3+wkwPeObwS4R37FGXMYViqc4iTrIRj5yzfX9Qy4mnpZ26sX41dGMhsAgmKks1r/uufY1pl4vpgzMWHYfJRb2A==} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -3753,9 +3426,6 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browser-assert@1.2.1: - resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} - browserify-aes@1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} @@ -3781,9 +3451,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -3800,8 +3467,8 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} - caching-transform@4.0.0: - resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} call-bind-apply-helpers@1.0.2: @@ -3816,6 +3483,9 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3827,19 +3497,11 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001721: - resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==} + caniuse-lite@1.0.30001723: + resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} @@ -3852,10 +3514,6 @@ packages: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@3.0.0: resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} engines: {node: '>=8'} @@ -3864,18 +3522,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - char-regex@2.0.2: - resolution: {integrity: sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==} - engines: {node: '>=12.20'} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -3896,6 +3542,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chromatic@11.25.2: resolution: {integrity: sha512-/9eQWn6BU1iFsop86t8Au21IksTRxwXAl7if8YHD05L2AbuMjClLWZo5cZojqrJHGKDhTqfrC2X2xE4uSm0iKw==} hasBin: true @@ -3908,14 +3558,22 @@ packages: '@chromatic-com/playwright': optional: true + chromatic@12.2.0: + resolution: {integrity: sha512-GswmBW9ZptAoTns1BMyjbm55Z7EsIJnUvYKdQqXIBZIKbGErmpA+p4c0BYA+nzw5B0M+rb3Iqp1IaH8TFwIQew==} + hasBin: true + peerDependencies: + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + peerDependenciesMeta: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': + optional: true + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - cipher-base@1.0.6: resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} engines: {node: '>= 0.10'} @@ -3933,10 +3591,6 @@ packages: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -3944,9 +3598,6 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3961,23 +3612,10 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -3991,23 +3629,12 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@3.0.2: - resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -4022,6 +3649,9 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -4050,11 +3680,11 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} - core-js-compat@3.42.0: - resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==} + core-js-compat@3.43.0: + resolution: {integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==} - core-js-pure@3.42.0: - resolution: {integrity: sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==} + core-js-pure@3.43.0: + resolution: {integrity: sha512-i/AgxU2+A+BbJdMxh3v7/vxi2SbFqxiFmg6VsDwYB4jkucrd1BZNA9a9gphC0fYMG5IBSgQcbQnk865VCLe7xA==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -4081,11 +3711,6 @@ packages: create-hmac@1.1.7: resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4131,10 +3756,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - cwd@0.10.0: - resolution: {integrity: sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==} - engines: {node: '>=0.8'} - d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} @@ -4239,27 +3860,15 @@ packages: supports-color: optional: true - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - decode-named-character-reference@1.1.0: - resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - dedent@1.6.0: - resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -4275,10 +3884,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - default-require-extensions@3.0.1: - resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} - engines: {node: '>=8'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -4291,9 +3896,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} + dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} @@ -4306,10 +3911,6 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -4319,16 +3920,13 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diffable-html@4.1.0: - resolution: {integrity: sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==} - diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -4352,9 +3950,6 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dom-serializer@0.2.2: - resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} - dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} @@ -4362,22 +3957,13 @@ packages: resolution: {integrity: sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==} engines: {node: '>=10'} - domelementtype@1.3.1: - resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} - domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - domhandler@2.4.2: - resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} - domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} - domutils@1.7.0: - resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} - domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -4395,8 +3981,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.165: - resolution: {integrity: sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==} + electron-to-chromium@1.5.169: + resolution: {integrity: sha512-q7SQx6mkLy0GTJK9K9OiWeaBMV4XQtBSdf6MJUzDB/H/5tFXfIiX38Lci1Kl6SsgiEhz1SQI1ejEOU5asWEhwQ==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -4414,10 +4000,6 @@ packages: embla-carousel@8.6.0: resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -4435,12 +4017,17 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} - entities@1.1.2: - resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4455,6 +4042,10 @@ packages: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} + es-aggregate-error@1.0.14: + resolution: {integrity: sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -4486,16 +4077,16 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: esbuild: '>=0.12 <1' - esbuild@0.24.2: - resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} hasBin: true @@ -4503,20 +4094,12 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@15.3.3: - resolution: {integrity: sha512-QJLv/Ouk2vZnxL4b67njJwTLjTf7uZRltI0LL4GERYR4qMF5z08+gxkfODAeaK7TiC6o+cER91bDaEnwrTWV6Q==} + eslint-config-next@15.3.4: + resolution: {integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -4589,11 +4172,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-storybook@0.12.0: - resolution: {integrity: sha512-Lg5I0+npTgiYgZ4KSvGWGDFZi3eOCNJPaWX0c9rTEEXC5wvooOClsP9ZtbI4hhFKyKgYR877KiJxbRTSJq9gWA==} - engines: {node: '>= 18'} + eslint-plugin-storybook@9.0.12: + resolution: {integrity: sha512-dSzcozoI7tQRqfMODWfxahrRmKWsK88yKSUcO0+s361oYcX7nf8nEu99TQ/wuDLRHh+Zi7E2j43cPMH8gFo8OA==} + engines: {node: '>=20.0.0'} peerDependencies: eslint: '>=8' + storybook: ^9.0.12 eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -4607,8 +4191,8 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@8.57.1: @@ -4648,9 +4232,6 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4676,21 +4257,6 @@ packages: exenv@1.2.2: resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expand-tilde@1.2.2: - resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==} - engines: {node: '>=0.10.0'} - - expect-playwright@0.8.0: - resolution: {integrity: sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -4721,17 +4287,20 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-memoize@2.5.2: + resolution: {integrity: sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - fdir@6.4.5: - resolution: {integrity: sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -4762,18 +4331,6 @@ packages: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} - find-file-up@0.1.3: - resolution: {integrity: sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==} - engines: {node: '>=0.10.0'} - - find-pkg@0.1.2: - resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==} - engines: {node: '>=0.10.0'} - - find-process@1.4.10: - resolution: {integrity: sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==} - hasBin: true - find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -4786,6 +4343,10 @@ packages: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4793,23 +4354,10 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - foreground-child@2.0.0: - resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} - engines: {node: '>=8.0.0'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -4821,10 +4369,6 @@ packages: typescript: '>3.6.0' webpack: ^5.11.0 - form-data@4.0.3: - resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} - engines: {node: '>= 6'} - forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -4842,17 +4386,14 @@ packages: react-dom: optional: true - fromentries@1.3.2: - resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} - - fs-exists-sync@0.1.0: - resolution: {integrity: sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==} - engines: {node: '>=0.10.0'} - fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -4900,10 +4441,6 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -4942,14 +4479,6 @@ packages: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} - global-modules@0.2.3: - resolution: {integrity: sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==} - engines: {node: '>=0.10.0'} - - global-prefix@0.1.5: - resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} - engines: {node: '>=0.10.0'} - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -4962,6 +4491,10 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -4980,10 +4513,6 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -5010,10 +4539,6 @@ packages: hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - hasha@5.2.2: - resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} - engines: {node: '>=8'} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -5037,16 +4562,9 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - homedir-polyfill@1.0.3: - resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} - engines: {node: '>=0.10.0'} - html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -5067,12 +4585,12 @@ packages: webpack: optional: true - htmlparser2@3.10.1: - resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} - htmlparser2@6.1.0: resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + http2-client@1.3.5: + resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==} + https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} @@ -5101,22 +4619,20 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - image-size@1.2.1: - resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} + image-size@2.0.2: + resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} engines: {node: '>=16.x'} hasBin: true + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@1.14.0: - resolution: {integrity: sha512-g5zLT0HaztRJWysayWYiUq/7E5H825QIiecMD2pI5QO7Wzr847l6GDvPvmZaDIdrDtS2w7qRczywxiK6SL5vRw==} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true + import-in-the-middle@1.14.2: + resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -5133,9 +4649,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -5222,10 +4735,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -5299,9 +4808,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -5314,14 +4820,6 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} - is-windows@0.2.0: - resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} - engines: {node: '>=0.10.0'} - - is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -5335,42 +4833,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-hook@3.0.0: - resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} - engines: {node: '>=8'} - - istanbul-lib-instrument@4.0.3: - resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - - istanbul-lib-processinfo@2.0.3: - resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -5381,184 +4843,24 @@ packages: jaro-winkler@0.2.8: resolution: {integrity: sha512-yr+mElb6dWxA1mzFu0+26njV5DWAQRNTi5pB6fFMm79zHrfAs3d0qjhe/IpZI4AHIUJkzvu5QXQRWOw2O0GQyw==} - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-junit@16.0.0: - resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} - engines: {node: '>=10.12.0'} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-playwright-preset@4.0.0: - resolution: {integrity: sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==} - peerDependencies: - jest: ^29.3.1 - jest-circus: ^29.3.1 - jest-environment-node: ^29.3.1 - jest-runner: ^29.3.1 - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-process-manager@0.4.0: - resolution: {integrity: sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==} - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-serializer-html@7.1.0: - resolution: {integrity: sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watch-typeahead@2.2.2: - resolution: {integrity: sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==} - engines: {node: ^14.17.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - jest: ^27.0.0 || ^28.0.0 || ^29.0.0 - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - joi@17.13.3: - resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsdoc-type-pratt-parser@4.1.0: - resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} - engines: {node: '>=12.0.0'} + jsep@1.4.0: + resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} + engines: {node: '>= 10.16.0'} jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} @@ -5594,12 +4896,24 @@ packages: engines: {node: '>=6'} hasBin: true - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonc-parser@2.2.1: + resolution: {integrity: sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==} jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonpath-plus@10.3.0: + resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} + engines: {node: '>=18.0.0'} + hasBin: true + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + jsonschema@1.5.0: + resolution: {integrity: sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -5611,10 +4925,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -5649,6 +4959,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -5679,15 +4992,33 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.flattendeep@4.4.0: - resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + lodash.isempty@4.4.0: + resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.omitby@4.6.0: + resolution: {integrity: sha512-5OrRcIVR75M288p4nbI2WLAf3ndw2GD9fyNv3Bc15+WCxJDdZ4lYndSxGd7hnG6PVjiJTeJE2dHEGhIuKGicIQ==} + + lodash.topath@4.5.2: + resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + + lodash.uniqwith@4.5.0: + resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loglevel-plugin-prefix@0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + loglevel@1.9.2: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} @@ -5699,8 +5030,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -5716,6 +5047,9 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -5731,15 +5065,9 @@ packages: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - map-or-similar@1.5.0: - resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} @@ -5772,13 +5100,13 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + memfs@3.5.3: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - memoizerific@1.11.3: - resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -5882,6 +5210,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@6.2.0: + resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} + engines: {node: '>=10'} + minimatch@8.0.4: resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} engines: {node: '>=16 || 14 >=14.17'} @@ -5901,22 +5233,17 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - motion-dom@12.16.0: - resolution: {integrity: sha512-Z2nGwWrrdH4egLEtgYMCEN4V2qQt1qxlKy/uV7w691ztyA41Q5Rbn0KNGbsNVDZr9E8PD2IOQ3hSccRnB6xWzw==} + motion-dom@12.18.1: + resolution: {integrity: sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==} - motion-utils@12.12.1: - resolution: {integrity: sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==} + motion-utils@12.18.1: + resolution: {integrity: sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5990,12 +5317,20 @@ packages: sass: optional: true + nimma@0.2.3: + resolution: {integrity: sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==} + engines: {node: ^12.20 || >=14.13} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-fetch-h2@2.3.0: + resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==} + engines: {node: 4.x || >=6.0.0} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -6005,18 +5340,14 @@ packages: encoding: optional: true - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-polyfill-webpack-plugin@2.0.1: resolution: {integrity: sha512-ZUMiCnZkP1LF0Th2caY6J/eKKoA0TefpoVa68m/LQU1I/mE8rGt4fNYGgNuCcK+aG8P8P43nbeJ2RqJMOL/Y1A==} engines: {node: '>=12'} peerDependencies: webpack: '>=5' - node-preload@0.2.1: - resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} - engines: {node: '>=8'} + node-readfiles@0.2.0: + resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -6032,11 +5363,22 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nyc@15.1.0: - resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} - engines: {node: '>=8.9'} + oas-kit-common@1.0.8: + resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} + + oas-linter@3.2.2: + resolution: {integrity: sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==} + + oas-resolver@2.5.6: + resolution: {integrity: sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==} hasBin: true + oas-schema-walker@1.1.5: + resolution: {integrity: sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==} + + oas-validator@5.0.8: + resolution: {integrity: sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -6091,17 +5433,26 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + + openapi3-ts@4.2.2: + resolution: {integrity: sha512-+9g4actZKeb3czfi9gVQ4Br2Ju3KwhCAQJBNaKgye5KggqcBLIhFHH+nIkcm0BUX00TrAJl6dH4JWgM4G4JWrw==} + + openapi3-ts@4.4.0: + resolution: {integrity: sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orval@7.10.0: + resolution: {integrity: sha512-R1TlDDgK82dHfTXG0IuaIXHOrk6HQ1CuGejQQpQW9mBSCQA84AInp8U4Ovxw3upjMFNhghE8OlAQqD0ES8GgHQ==} + hasBin: true + os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} - os-homedir@1.0.2: - resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} - engines: {node: '>=0.10.0'} - outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -6133,18 +5484,10 @@ packages: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-map@3.0.0: - resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} - engines: {node: '>=8'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-hash@4.0.0: - resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} - engines: {node: '>=8'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -6169,10 +5512,6 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse-passwd@1.0.0: - resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} - engines: {node: '>=0.10.0'} - party-js@2.2.0: resolution: {integrity: sha512-50hGuALCpvDTrQLPQ1fgUgxKIWAH28ShVkmeK/3zhO0YJyCqkhrZhQEkWPxDYLvbFJ7YAXyROmFEu35gKpZLtQ==} @@ -6258,23 +5597,19 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - playwright-core@1.52.0: - resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} + playwright-core@1.53.1: + resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==} engines: {node: '>=18'} hasBin: true - playwright@1.52.0: - resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} + playwright@1.53.1: + resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==} engines: {node: '>=18'} hasBin: true - pnp-webpack-plugin@1.7.0: - resolution: {integrity: sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg==} - engines: {node: '>=6'} - - polished@4.3.1: - resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} - engines: {node: '>=10'} + pony-cause@1.1.1: + resolution: {integrity: sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==} + engines: {node: '>=12.0.0'} possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} @@ -6366,8 +5701,8 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.4: - resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -6457,17 +5792,9 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process-on-spawn@1.1.0: - resolution: {integrity: sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==} - engines: {node: '>=8'} - process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -6476,10 +5803,6 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -6495,6 +5818,10 @@ packages: public-encrypt@4.0.3: resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -6502,9 +5829,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -6519,9 +5843,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -6532,20 +5853,14 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - react-confetti@6.4.0: - resolution: {integrity: sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==} - engines: {node: '>=16'} - peerDependencies: - react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 - react-day-picker@9.7.0: resolution: {integrity: sha512-urlK4C9XJZVpQ81tmVgd2O7lZ0VQldZeHzNejbwLWZSkzHH498KnArT0EHNfKBOWwKc935iMLGZdxXPRISzUxQ==} engines: {node: '>=18'} peerDependencies: react: '>=16.8.0' - react-docgen-typescript@2.2.2: - resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} peerDependencies: typescript: '>= 4.3.x' @@ -6674,6 +5989,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + recast@0.23.11: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} @@ -6696,6 +6015,9 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + reftools@1.1.9: + resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} + regenerate-unicode-properties@10.2.0: resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} engines: {node: '>=4'} @@ -6725,10 +6047,6 @@ packages: resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} engines: {node: '>= 0.10'} - release-zalgo@1.0.0: - resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} - engines: {node: '>=4'} - remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -6750,28 +6068,13 @@ packages: resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} engines: {node: '>=8.6.0'} - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-dir@0.1.1: - resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} - engines: {node: '>=0.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -6779,10 +6082,6 @@ packages: resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} engines: {node: '>=12'} - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -6837,6 +6136,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@1.1.1: + resolution: {integrity: sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==} + sass-loader@14.2.1: resolution: {integrity: sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==} engines: {node: '>= 18.12.0'} @@ -6881,9 +6183,6 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -6906,10 +6205,6 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - sharp@0.34.2: resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -6933,6 +6228,24 @@ packages: shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + should-equal@2.0.0: + resolution: {integrity: sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==} + + should-format@3.0.3: + resolution: {integrity: sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==} + + should-type-adaptors@1.1.0: + resolution: {integrity: sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==} + + should-type@1.4.0: + resolution: {integrity: sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==} + + should-util@1.0.1: + resolution: {integrity: sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==} + + should@13.2.3: + resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -6956,27 +6269,21 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-eval@1.0.1: + resolution: {integrity: sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==} + engines: {node: '>=12'} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -6991,23 +6298,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spawn-wrap@2.0.0: - resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} - engines: {node: '>=8'} - - spawnd@5.0.0: - resolution: {integrity: sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -7023,8 +6316,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - storybook@8.6.14: - resolution: {integrity: sha512-sVKbCj/OTx67jhmauhxc2dcr1P+yOgz/x3h0krwjyMgdc5Oubvxyg4NYDZmzAw+ym36g/lzH8N0Ccp4dwtdfxw==} + storybook@9.0.12: + resolution: {integrity: sha512-mpACe6BMd/M5sqcOiA8NmWIm2zdx0t4ujnA4NTcq4aErdK/KKuU255UM4pO3DIf5zWR5VrDfNV5UaMi/VgE2mA==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -7045,13 +6338,9 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-length@5.0.1: - resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} - engines: {node: '>=12.20'} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -7105,10 +6394,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -7131,14 +6416,14 @@ packages: peerDependencies: webpack: ^5.0.0 - style-to-js@1.1.16: - resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} + style-to-js@1.1.17: + resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} - style-to-object@1.0.8: - resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + style-to-object@1.0.9: + resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} - styled-components@6.1.18: - resolution: {integrity: sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==} + styled-components@6.1.19: + resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -7178,10 +6463,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -7194,6 +6475,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swagger2openapi@7.0.8: + resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} + hasBin: true + tailwind-merge@2.6.0: resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} @@ -7227,15 +6512,11 @@ packages: uglify-js: optional: true - terser@5.41.0: - resolution: {integrity: sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==} + terser@5.42.0: + resolution: {integrity: sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==} engines: {node: '>=10'} hasBin: true - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -7260,17 +6541,14 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -7305,11 +6583,12 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-pnp@1.2.0: - resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==} - engines: {node: '>=6'} + tsconfck@2.1.2: + resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} + engines: {node: ^14.13.1 || ^16 || >=18} + hasBin: true peerDependencies: - typescript: '*' + typescript: ^4.3.5 || ^5.0.0 peerDependenciesMeta: typescript: optional: true @@ -7325,6 +6604,9 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -7334,17 +6616,10 @@ packages: tty-browserify@0.0.1: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} - tween-functions@1.2.0: - resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -7357,10 +6632,6 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -7385,14 +6656,27 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + typedoc-plugin-markdown@4.6.4: + resolution: {integrity: sha512-AnbToFS1T1H+n40QbO2+i0wE6L+55rWnj7zxnM1r781+2gmhMF2dB6dzFpaylWLQYkbg4D1Y13sYnne/6qZwdw==} + engines: {node: '>= 18'} + peerDependencies: + typedoc: 0.28.x + + typedoc@0.28.5: + resolution: {integrity: sha512-5PzUddaA9FbaarUzIsEc4wNXCiO4Ot3bJNeMF2qKpYlTmM9TTaSHQ7162w756ERCkXER/+o2purRG6YOAv6EMA==} + engines: {node: '>= 18', pnpm: '>= 10'} + hasBin: true + peerDependencies: + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -7416,6 +6700,10 @@ packages: resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} engines: {node: '>=4'} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -7449,8 +6737,8 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} - unrs-resolver@1.7.10: - resolution: {integrity: sha512-CJEMJcz6vuwRK6xxWc+uf8AGi0OyfoVtHs5mExtNecS0HZq3a3Br1JC/InwwTn6uy+qkAdAdK+nJUYO9FPtgZw==} + unrs-resolver@1.9.0: + resolution: {integrity: sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==} update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} @@ -7461,6 +6749,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} @@ -7502,6 +6793,10 @@ packages: utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true @@ -7514,9 +6809,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} + validator@13.15.15: + resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + engines: {node: '>= 0.10'} vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -7530,19 +6825,6 @@ packages: vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} - engines: {node: '>=12.0.0'} - hasBin: true - - wait-port@0.2.14: - resolution: {integrity: sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==} - engines: {node: '>=8'} - hasBin: true - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -7600,17 +6882,10 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.19: resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} engines: {node: '>= 0.4'} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -7635,13 +6910,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -7654,9 +6922,6 @@ packages: utf-8-validate: optional: true - xml@1.0.1: - resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} - xmlbuilder@15.1.1: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} @@ -7665,9 +6930,6 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -7684,18 +6946,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -7744,6 +6998,31 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 + '@apidevtools/json-schema-ref-parser@11.7.2': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@10.1.1(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 11.7.2 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + + '@asyncapi/specs@6.8.1': + dependencies: + '@types/json-schema': 7.0.15 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -7939,26 +7218,11 @@ snapshots: dependencies: '@babel/core': 7.27.4 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -7974,61 +7238,11 @@ snapshots: '@babel/core': 7.27.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -8481,7 +7695,7 @@ snapshots: babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.27.4) babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.27.4) babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.27.4) - core-js-compat: 3.42.0 + core-js-compat: 3.43.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -8541,8 +7755,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@bcoe/v8-coverage@0.2.3': {} - '@bundled-es-modules/cookie@2.0.1': dependencies: cookie: 0.7.2 @@ -8556,18 +7768,17 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@chromatic-com/storybook@3.2.6(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))': + '@chromatic-com/storybook@4.0.0(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: - chromatic: 11.25.2 + '@neoconfetti/react': 1.0.0 + chromatic: 12.2.0 filesize: 10.1.6 jsonfile: 6.1.0 - react-confetti: 6.4.0(react@18.3.1) - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) strip-ansi: 7.1.0 transitivePeerDependencies: - '@chromatic-com/cypress' - '@chromatic-com/playwright' - - react '@date-fns/tz@1.2.0': {} @@ -8595,79 +7806,79 @@ snapshots: '@emotion/unitless@0.8.1': {} - '@esbuild/aix-ppc64@0.24.2': + '@esbuild/aix-ppc64@0.25.5': optional: true - '@esbuild/android-arm64@0.24.2': + '@esbuild/android-arm64@0.25.5': optional: true - '@esbuild/android-arm@0.24.2': + '@esbuild/android-arm@0.25.5': optional: true - '@esbuild/android-x64@0.24.2': + '@esbuild/android-x64@0.25.5': optional: true - '@esbuild/darwin-arm64@0.24.2': + '@esbuild/darwin-arm64@0.25.5': optional: true - '@esbuild/darwin-x64@0.24.2': + '@esbuild/darwin-x64@0.25.5': optional: true - '@esbuild/freebsd-arm64@0.24.2': + '@esbuild/freebsd-arm64@0.25.5': optional: true - '@esbuild/freebsd-x64@0.24.2': + '@esbuild/freebsd-x64@0.25.5': optional: true - '@esbuild/linux-arm64@0.24.2': + '@esbuild/linux-arm64@0.25.5': optional: true - '@esbuild/linux-arm@0.24.2': + '@esbuild/linux-arm@0.25.5': optional: true - '@esbuild/linux-ia32@0.24.2': + '@esbuild/linux-ia32@0.25.5': optional: true - '@esbuild/linux-loong64@0.24.2': + '@esbuild/linux-loong64@0.25.5': optional: true - '@esbuild/linux-mips64el@0.24.2': + '@esbuild/linux-mips64el@0.25.5': optional: true - '@esbuild/linux-ppc64@0.24.2': + '@esbuild/linux-ppc64@0.25.5': optional: true - '@esbuild/linux-riscv64@0.24.2': + '@esbuild/linux-riscv64@0.25.5': optional: true - '@esbuild/linux-s390x@0.24.2': + '@esbuild/linux-s390x@0.25.5': optional: true - '@esbuild/linux-x64@0.24.2': + '@esbuild/linux-x64@0.25.5': optional: true - '@esbuild/netbsd-arm64@0.24.2': + '@esbuild/netbsd-arm64@0.25.5': optional: true - '@esbuild/netbsd-x64@0.24.2': + '@esbuild/netbsd-x64@0.25.5': optional: true - '@esbuild/openbsd-arm64@0.24.2': + '@esbuild/openbsd-arm64@0.25.5': optional: true - '@esbuild/openbsd-x64@0.24.2': + '@esbuild/openbsd-x64@0.25.5': optional: true - '@esbuild/sunos-x64@0.24.2': + '@esbuild/sunos-x64@0.25.5': optional: true - '@esbuild/win32-arm64@0.24.2': + '@esbuild/win32-arm64@0.25.5': optional: true - '@esbuild/win32-ia32@0.24.2': + '@esbuild/win32-ia32@0.25.5': optional: true - '@esbuild/win32-x64@0.24.2': + '@esbuild/win32-x64@0.25.5': optional: true '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': @@ -8693,6 +7904,8 @@ snapshots: '@eslint/js@8.57.1': {} + '@exodus/schemasafe@1.3.0': {} + '@faker-js/faker@9.8.0': {} '@floating-ui/core@1.7.1': @@ -8712,11 +7925,13 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@hapi/hoek@9.3.0': {} - - '@hapi/topo@5.1.0': + '@gerrit0/mini-shiki@3.6.0': dependencies: - '@hapi/hoek': 9.3.0 + '@shikijs/engine-oniguruma': 3.6.0 + '@shikijs/langs': 3.6.0 + '@shikijs/themes': 3.6.0 + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 '@hookform/resolvers@5.1.1(react-hook-form@7.57.0(react@18.3.1))': dependencies: @@ -8735,142 +7950,91 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true + '@ibm-cloud/openapi-ruleset-utilities@1.9.0': {} + + '@ibm-cloud/openapi-ruleset@1.31.1': + dependencies: + '@ibm-cloud/openapi-ruleset-utilities': 1.9.0 + '@stoplight/spectral-formats': 1.8.2 + '@stoplight/spectral-functions': 1.10.1 + '@stoplight/spectral-rulesets': 1.22.0 + chalk: 4.1.2 + jsonschema: 1.5.0 + lodash: 4.17.21 + loglevel: 1.9.2 + loglevel-plugin-prefix: 0.8.4 + minimatch: 6.2.0 + validator: 13.15.15 + transitivePeerDependencies: + - encoding '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - '@img/sharp-darwin-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - '@img/sharp-libvips-linux-arm@1.1.0': optional: true '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - '@img/sharp-linux-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - '@img/sharp-linux-arm@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - '@img/sharp-linux-s390x@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - '@img/sharp-linux-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.4.3 - optional: true - '@img/sharp-wasm32@0.34.2': dependencies: '@emnapi/runtime': 1.4.3 @@ -8879,15 +8043,9 @@ snapshots: '@img/sharp-win32-arm64@0.34.2': optional: true - '@img/sharp-win32-ia32@0.33.5': - optional: true - '@img/sharp-win32-ia32@0.34.2': optional: true - '@img/sharp-win32-x64@0.33.5': - optional: true - '@img/sharp-win32-x64@0.34.2': optional: true @@ -8926,182 +8084,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@istanbuljs/load-nyc-config@1.1.0': - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - '@istanbuljs/schema@0.1.3': {} - - '@jest/console@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - '@jest/core@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.15.30) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/create-cache-key-function@29.7.0': - dependencies: - '@jest/types': 29.6.3 - - '@jest/environment@29.7.0': - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - jest-mock: 29.7.0 - - '@jest/expect-utils@29.7.0': - dependencies: - jest-get-type: 29.6.3 - - '@jest/expect@29.7.0': - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/fake-timers@29.7.0': - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.15.30 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - '@jest/globals@29.7.0': - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - '@jest/reporters@29.7.0': - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 22.15.30 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.3.0 - transitivePeerDependencies: - - supports-color - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jest/source-map@29.6.3': - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - '@jest/test-result@29.7.0': - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 - - '@jest/test-sequencer@29.7.0': - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 - - '@jest/transform@29.7.0': - dependencies: - '@babel/core': 7.27.4 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.8 - pirates: 4.0.7 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - '@jest/types@29.6.3': - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 22.15.30 - '@types/yargs': 17.0.33 - chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -9124,6 +8106,20 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsdevtools/ono@7.1.3': {} + + '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + + '@jsep-plugin/ternary@1.1.4(jsep@1.4.0)': + dependencies: + jsep: 1.4.0 + '@mdx-js/react@3.1.0(@types/react@18.3.17)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 @@ -9139,16 +8135,18 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@0.2.10': + '@napi-rs/wasm-runtime@0.2.11': dependencies: '@emnapi/core': 1.4.3 '@emnapi/runtime': 1.4.3 '@tybys/wasm-util': 0.9.0 optional: true + '@neoconfetti/react@1.0.0': {} + '@next/env@15.3.3': {} - '@next/eslint-plugin-next@15.3.3': + '@next/eslint-plugin-next@15.3.4': dependencies: fast-glob: 3.3.1 @@ -9176,9 +8174,9 @@ snapshots: '@next/swc-win32-x64-msvc@15.3.3': optional: true - '@next/third-parties@15.3.3(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@next/third-parties@15.3.3(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: - next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 third-party-capital: 1.0.20 @@ -9416,7 +8414,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.14.0 + import-in-the-middle: 1.14.2 require-in-the-middle: 7.5.2 semver: 7.7.2 shimmer: 1.2.1 @@ -9447,24 +8445,134 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@orval/angular@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/axios@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/core@7.10.0(openapi-types@12.1.3)': + dependencies: + '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) + '@ibm-cloud/openapi-ruleset': 1.31.1 + acorn: 8.15.0 + ajv: 8.17.1 + chalk: 4.1.2 + compare-versions: 6.1.1 + debug: 4.4.1 + esbuild: 0.25.5 + esutils: 2.0.3 + fs-extra: 11.3.0 + globby: 11.1.0 + lodash.isempty: 4.4.0 + lodash.uniq: 4.5.0 + lodash.uniqby: 4.7.0 + lodash.uniqwith: 4.5.0 + micromatch: 4.0.8 + openapi3-ts: 4.4.0 + swagger2openapi: 7.0.8 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/fetch@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/hono@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + '@orval/zod': 7.10.0(openapi-types@12.1.3) + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/mcp@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + '@orval/zod': 7.10.0(openapi-types@12.1.3) + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/mock@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + openapi3-ts: 4.2.2 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/query@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + '@orval/fetch': 7.10.0(openapi-types@12.1.3) + lodash.omitby: 4.6.0 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/swr@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + '@orval/fetch': 7.10.0(openapi-types@12.1.3) + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@orval/zod@7.10.0(openapi-types@12.1.3)': + dependencies: + '@orval/core': 7.10.0(openapi-types@12.1.3) + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color + + '@phosphor-icons/react@2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.52.0': + '@playwright/test@1.53.1': dependencies: - playwright: 1.52.0 + playwright: 1.53.1 - '@pmmmwh/react-refresh-webpack-plugin@0.5.16(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.16(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(esbuild@0.25.5))': dependencies: ansi-html: 0.0.9 - core-js-pure: 3.42.0 + core-js-pure: 3.43.0 error-stack-parser: 2.1.4 html-entities: 2.6.0 loader-utils: 2.0.4 react-refresh: 0.14.2 schema-utils: 4.3.2 source-map: 0.7.4 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) optionalDependencies: type-fest: 4.41.0 webpack-hot-middleware: 2.26.1 @@ -10020,7 +9128,7 @@ snapshots: '@rollup/pluginutils': 5.1.4(rollup@4.35.0) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.4.5(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) is-reference: 1.2.1 magic-string: 0.30.17 picomatch: 4.0.2 @@ -10182,7 +9290,7 @@ snapshots: '@sentry/core@9.27.0': {} - '@sentry/nextjs@9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2))': + '@sentry/nextjs@9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.99.9(esbuild@0.25.5))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.34.0 @@ -10193,9 +9301,9 @@ snapshots: '@sentry/opentelemetry': 9.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0) '@sentry/react': 9.27.0(react@18.3.1) '@sentry/vercel-edge': 9.27.0 - '@sentry/webpack-plugin': 3.5.0(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + '@sentry/webpack-plugin': 3.5.0(webpack@5.99.9(esbuild@0.25.5)) chalk: 3.0.0 - next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) resolve: 1.22.8 rollup: 4.35.0 stacktrace-parser: 0.1.11 @@ -10243,7 +9351,7 @@ snapshots: '@prisma/instrumentation': 6.8.2(@opentelemetry/api@1.9.0) '@sentry/core': 9.27.0 '@sentry/opentelemetry': 9.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0) - import-in-the-middle: 1.14.0 + import-in-the-middle: 1.14.2 minimatch: 9.0.5 transitivePeerDependencies: - supports-color @@ -10270,177 +9378,247 @@ snapshots: '@opentelemetry/api': 1.9.0 '@sentry/core': 9.27.0 - '@sentry/webpack-plugin@3.5.0(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2))': + '@sentry/webpack-plugin@3.5.0(webpack@5.99.9(esbuild@0.25.5))': dependencies: '@sentry/bundler-plugin-core': 3.5.0 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) transitivePeerDependencies: - encoding - supports-color - '@sideway/address@4.1.5': + '@shikijs/engine-oniguruma@3.6.0': dependencies: - '@hapi/hoek': 9.3.0 + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 - '@sideway/formula@3.0.1': {} - - '@sideway/pinpoint@2.0.0': {} - - '@sinclair/typebox@0.27.8': {} - - '@sinonjs/commons@3.0.1': + '@shikijs/langs@3.6.0': dependencies: - type-detect: 4.0.8 + '@shikijs/types': 3.6.0 - '@sinonjs/fake-timers@10.3.0': + '@shikijs/themes@3.6.0': dependencies: - '@sinonjs/commons': 3.0.1 + '@shikijs/types': 3.6.0 + + '@shikijs/types@3.6.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} '@standard-schema/utils@0.3.0': {} - '@storybook/addon-a11y@8.6.14(storybook@8.6.14(prettier@3.5.3))': + '@stoplight/better-ajv-errors@1.0.3(ajv@8.17.1)': + dependencies: + ajv: 8.17.1 + jsonpointer: 5.0.1 + leven: 3.1.0 + + '@stoplight/json-ref-readers@1.2.2': + dependencies: + node-fetch: 2.7.0 + tslib: 1.14.1 + transitivePeerDependencies: + - encoding + + '@stoplight/json-ref-resolver@3.1.6': + dependencies: + '@stoplight/json': 3.21.7 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.6.0 + '@types/urijs': 1.19.25 + dependency-graph: 0.11.0 + fast-memoize: 2.5.2 + immer: 9.0.21 + lodash: 4.17.21 + tslib: 2.8.1 + urijs: 1.19.11 + + '@stoplight/json@3.21.7': + dependencies: + '@stoplight/ordered-object-literal': 1.0.5 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.20.0 + jsonc-parser: 2.2.1 + lodash: 4.17.21 + safe-stable-stringify: 1.1.1 + + '@stoplight/ordered-object-literal@1.0.5': {} + + '@stoplight/path@1.3.2': {} + + '@stoplight/spectral-core@1.20.0': + dependencies: + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1) + '@stoplight/json': 3.21.7 + '@stoplight/path': 1.3.2 + '@stoplight/spectral-parsers': 1.0.5 + '@stoplight/spectral-ref-resolver': 1.0.5 + '@stoplight/spectral-runtime': 1.1.4 + '@stoplight/types': 13.6.0 + '@types/es-aggregate-error': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-errors: 3.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + es-aggregate-error: 1.0.14 + jsonpath-plus: 10.3.0 + lodash: 4.17.21 + lodash.topath: 4.5.2 + minimatch: 3.1.2 + nimma: 0.2.3 + pony-cause: 1.1.1 + simple-eval: 1.0.1 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/spectral-formats@1.8.2': + dependencies: + '@stoplight/json': 3.21.7 + '@stoplight/spectral-core': 1.20.0 + '@types/json-schema': 7.0.15 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/spectral-functions@1.10.1': + dependencies: + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1) + '@stoplight/json': 3.21.7 + '@stoplight/spectral-core': 1.20.0 + '@stoplight/spectral-formats': 1.8.2 + '@stoplight/spectral-runtime': 1.1.4 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-errors: 3.0.0(ajv@8.17.1) + ajv-formats: 2.1.1(ajv@8.17.1) + lodash: 4.17.21 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/spectral-parsers@1.0.5': + dependencies: + '@stoplight/json': 3.21.7 + '@stoplight/types': 14.1.1 + '@stoplight/yaml': 4.3.0 + tslib: 2.8.1 + + '@stoplight/spectral-ref-resolver@1.0.5': + dependencies: + '@stoplight/json-ref-readers': 1.2.2 + '@stoplight/json-ref-resolver': 3.1.6 + '@stoplight/spectral-runtime': 1.1.4 + dependency-graph: 0.11.0 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/spectral-rulesets@1.22.0': + dependencies: + '@asyncapi/specs': 6.8.1 + '@stoplight/better-ajv-errors': 1.0.3(ajv@8.17.1) + '@stoplight/json': 3.21.7 + '@stoplight/spectral-core': 1.20.0 + '@stoplight/spectral-formats': 1.8.2 + '@stoplight/spectral-functions': 1.10.1 + '@stoplight/spectral-runtime': 1.1.4 + '@stoplight/types': 13.20.0 + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + json-schema-traverse: 1.0.0 + leven: 3.1.0 + lodash: 4.17.21 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/spectral-runtime@1.1.4': + dependencies: + '@stoplight/json': 3.21.7 + '@stoplight/path': 1.3.2 + '@stoplight/types': 13.20.0 + abort-controller: 3.0.0 + lodash: 4.17.21 + node-fetch: 2.7.0 + tslib: 2.8.1 + transitivePeerDependencies: + - encoding + + '@stoplight/types@13.20.0': + dependencies: + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 + + '@stoplight/types@13.6.0': + dependencies: + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 + + '@stoplight/types@14.1.1': + dependencies: + '@types/json-schema': 7.0.15 + utility-types: 3.11.0 + + '@stoplight/yaml-ast-parser@0.0.50': {} + + '@stoplight/yaml@4.3.0': + dependencies: + '@stoplight/ordered-object-literal': 1.0.5 + '@stoplight/types': 14.1.1 + '@stoplight/yaml-ast-parser': 0.0.50 + tslib: 2.8.1 + + '@storybook/addon-a11y@9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: - '@storybook/addon-highlight': 8.6.14(storybook@8.6.14(prettier@3.5.3)) '@storybook/global': 5.0.0 - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) axe-core: 4.10.3 - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) - '@storybook/addon-actions@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - '@types/uuid': 9.0.8 - dequal: 2.0.3 - polished: 4.3.1 - storybook: 8.6.14(prettier@3.5.3) - uuid: 9.0.1 - - '@storybook/addon-backgrounds@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - memoizerific: 1.11.3 - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - - '@storybook/addon-controls@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - dequal: 2.0.3 - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - - '@storybook/addon-docs@8.6.14(@types/react@18.3.17)(storybook@8.6.14(prettier@3.5.3))': + '@storybook/addon-docs@9.0.12(@types/react@18.3.17)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: '@mdx-js/react': 3.1.0(@types/react@18.3.17)(react@18.3.1) - '@storybook/blocks': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - transitivePeerDependencies: - - '@types/react' - - '@storybook/addon-essentials@8.6.14(@types/react@18.3.17)(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-backgrounds': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-controls': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-docs': 8.6.14(@types/react@18.3.17)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-highlight': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-measure': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-outline': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-toolbars': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/addon-viewport': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - transitivePeerDependencies: - - '@types/react' - - '@storybook/addon-highlight@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/addon-interactions@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - polished: 4.3.1 - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - - '@storybook/addon-links@8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - optionalDependencies: - react: 18.3.1 - - '@storybook/addon-measure@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.5.3) - tiny-invariant: 1.3.3 - - '@storybook/addon-onboarding@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/addon-outline@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - - '@storybook/addon-toolbars@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/addon-viewport@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - memoizerific: 1.11.3 - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/blocks@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))': - dependencies: + '@storybook/csf-plugin': 9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) '@storybook/icons': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook: 8.6.14(prettier@3.5.3) - ts-dedent: 2.2.0 - optionalDependencies: + '@storybook/react-dom-shim': 9.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) + ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' - '@storybook/builder-webpack5@8.6.14(@swc/core@1.11.31)(esbuild@0.24.2)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3)': + '@storybook/addon-links@9.0.12(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: - '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@types/semver': 7.7.0 - browser-assert: 1.2.1 + '@storybook/global': 5.0.0 + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) + optionalDependencies: + react: 18.3.1 + + '@storybook/addon-onboarding@9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': + dependencies: + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) + + '@storybook/builder-webpack5@9.0.12(esbuild@0.25.5)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3)': + dependencies: + '@storybook/core-webpack': 9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 - constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + css-loader: 6.11.0(webpack@5.99.9(esbuild@0.25.5)) es-module-lexer: 1.7.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - html-webpack-plugin: 5.6.3(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5)) + html-webpack-plugin: 5.6.3(webpack@5.99.9(esbuild@0.25.5)) magic-string: 0.30.17 - path-browserify: 1.0.1 - process: 0.11.10 - semver: 7.7.2 - storybook: 8.6.14(prettier@3.5.3) - style-loader: 3.3.4(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - terser-webpack-plugin: 5.3.14(@swc/core@1.11.31)(esbuild@0.24.2)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) + style-loader: 3.3.4(webpack@5.99.9(esbuild@0.25.5)) + terser-webpack-plugin: 5.3.14(esbuild@0.25.5)(webpack@5.99.9(esbuild@0.25.5)) ts-dedent: 2.2.0 - url: 0.11.4 - util: 0.12.5 - util-deprecate: 1.0.2 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) - webpack-dev-middleware: 6.1.3(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + webpack: 5.99.9(esbuild@0.25.5) + webpack-dev-middleware: 6.1.3(webpack@5.99.9(esbuild@0.25.5)) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -10452,45 +9630,16 @@ snapshots: - uglify-js - webpack-cli - '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.5.3))': + '@storybook/core-webpack@9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/core-webpack@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) ts-dedent: 2.2.0 - '@storybook/core@8.6.14(prettier@3.5.3)(storybook@8.6.14(prettier@3.5.3))': + '@storybook/csf-plugin@9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: - '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - better-opn: 3.0.2 - browser-assert: 1.2.1 - esbuild: 0.24.2 - esbuild-register: 3.6.0(esbuild@0.24.2) - jsdoc-type-pratt-parser: 4.1.0 - process: 0.11.10 - recast: 0.23.11 - semver: 7.7.2 - util: 0.12.5 - ws: 8.18.2 - optionalDependencies: - prettier: 3.5.3 - transitivePeerDependencies: - - bufferutil - - storybook - - supports-color - - utf-8-validate - - '@storybook/csf-plugin@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) unplugin: 1.16.1 - '@storybook/csf@0.1.13': - dependencies: - type-fest: 2.19.0 - '@storybook/global@5.0.0': {} '@storybook/icons@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -10498,17 +9647,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/instrumenter@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - '@vitest/utils': 2.1.9 - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/manager-api@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/nextjs@8.6.14(@swc/core@1.11.31)(esbuild@0.24.2)(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(type-fest@4.41.0)(typescript@5.8.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2))': + '@storybook/nextjs@9.0.12(esbuild@0.25.5)(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(type-fest@4.41.0)(typescript@5.8.3)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(esbuild@0.25.5))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.4) @@ -10523,38 +9662,33 @@ snapshots: '@babel/preset-react': 7.27.1(@babel/core@7.27.4) '@babel/preset-typescript': 7.27.1(@babel/core@7.27.4) '@babel/runtime': 7.27.6 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.16(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - '@storybook/builder-webpack5': 8.6.14(@swc/core@1.11.31)(esbuild@0.24.2)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3) - '@storybook/preset-react-webpack': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(@swc/core@1.11.31)(esbuild@0.24.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3) - '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3) - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.16(react-refresh@0.14.2)(type-fest@4.41.0)(webpack-hot-middleware@2.26.1)(webpack@5.99.9(esbuild@0.25.5)) + '@storybook/builder-webpack5': 9.0.12(esbuild@0.25.5)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3) + '@storybook/preset-react-webpack': 9.0.12(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3) + '@storybook/react': 9.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3) '@types/semver': 7.7.0 - babel-loader: 9.2.1(@babel/core@7.27.4)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - css-loader: 6.11.0(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - find-up: 5.0.0 - image-size: 1.2.1 + babel-loader: 9.2.1(@babel/core@7.27.4)(webpack@5.99.9(esbuild@0.25.5)) + css-loader: 6.11.0(webpack@5.99.9(esbuild@0.25.5)) + image-size: 2.0.2 loader-utils: 3.3.1 - next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) - pnp-webpack-plugin: 1.7.0(typescript@5.8.3) - postcss: 8.5.4 - postcss-loader: 8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.99.9(esbuild@0.25.5)) + postcss: 8.5.6 + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + sass-loader: 14.2.1(webpack@5.99.9(esbuild@0.25.5)) semver: 7.7.2 - storybook: 8.6.14(prettier@3.5.3) - style-loader: 3.3.4(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) + style-loader: 3.3.4(webpack@5.99.9(esbuild@0.25.5)) styled-jsx: 5.1.7(@babel/core@7.27.4)(react@18.3.1) - ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 optionalDependencies: - sharp: 0.33.5 typescript: 5.8.3 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -10573,117 +9707,60 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(@swc/core@1.11.31)(esbuild@0.24.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3)': + '@storybook/preset-react-webpack@9.0.12(esbuild@0.25.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3)': dependencies: - '@storybook/core-webpack': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + '@storybook/core-webpack': 9.0.12(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5)) '@types/semver': 7.7.0 - find-up: 5.0.0 + find-up: 7.0.0 magic-string: 0.30.17 react: 18.3.1 react-docgen: 7.1.1 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.10 semver: 7.7.2 - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) tsconfig-paths: 4.2.0 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - - '@storybook/test' - '@swc/core' - esbuild - supports-color - uglify-js - webpack-cli - '@storybook/preview-api@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5))': dependencies: debug: 4.4.1 endent: 2.1.0 find-cache-dir: 3.3.2 flat-cache: 3.2.0 micromatch: 4.0.8 - react-docgen-typescript: 2.2.2(typescript@5.8.3) + react-docgen-typescript: 2.4.0(typescript@5.8.3) tslib: 2.8.1 typescript: 5.8.3 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))': + '@storybook/react-dom-shim@9.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) - '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3))(typescript@5.8.3)': + '@storybook/react@9.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3)': dependencies: - '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.5.3)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/preview-api': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.5.3)) - '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.5.3)) + '@storybook/react-dom-shim': 9.0.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@3.5.3) + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) optionalDependencies: - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.5.3)) typescript: 5.8.3 - '@storybook/test-runner@0.22.1(@types/node@22.15.30)(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@babel/core': 7.27.4 - '@babel/generator': 7.27.5 - '@babel/template': 7.27.2 - '@babel/types': 7.27.6 - '@jest/types': 29.6.3 - '@storybook/csf': 0.1.13 - '@swc/core': 1.11.31 - '@swc/jest': 0.2.38(@swc/core@1.11.31) - expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@22.15.30) - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-junit: 16.0.0 - jest-playwright-preset: 4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@22.15.30)) - jest-runner: 29.7.0 - jest-serializer-html: 7.1.0 - jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@22.15.30)) - nyc: 15.1.0 - playwright: 1.52.0 - storybook: 8.6.14(prettier@3.5.3) - transitivePeerDependencies: - - '@swc/helpers' - - '@types/node' - - babel-plugin-macros - - debug - - node-notifier - - supports-color - - ts-node - - '@storybook/test@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.5.3)) - '@testing-library/dom': 10.4.0 - '@testing-library/jest-dom': 6.5.0 - '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) - '@vitest/expect': 2.0.5 - '@vitest/spy': 2.0.5 - storybook: 8.6.14(prettier@3.5.3) - - '@storybook/theming@8.6.14(storybook@8.6.14(prettier@3.5.3))': - dependencies: - storybook: 8.6.14(prettier@3.5.3) - '@supabase/auth-js@2.70.0': dependencies: '@supabase/node-fetch': 2.6.15 @@ -10731,68 +9808,34 @@ snapshots: - bufferutil - utf-8-validate - '@swc/core-darwin-arm64@1.11.31': - optional: true - - '@swc/core-darwin-x64@1.11.31': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.11.31': - optional: true - - '@swc/core-linux-arm64-gnu@1.11.31': - optional: true - - '@swc/core-linux-arm64-musl@1.11.31': - optional: true - - '@swc/core-linux-x64-gnu@1.11.31': - optional: true - - '@swc/core-linux-x64-musl@1.11.31': - optional: true - - '@swc/core-win32-arm64-msvc@1.11.31': - optional: true - - '@swc/core-win32-ia32-msvc@1.11.31': - optional: true - - '@swc/core-win32-x64-msvc@1.11.31': - optional: true - - '@swc/core@1.11.31': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.22 - optionalDependencies: - '@swc/core-darwin-arm64': 1.11.31 - '@swc/core-darwin-x64': 1.11.31 - '@swc/core-linux-arm-gnueabihf': 1.11.31 - '@swc/core-linux-arm64-gnu': 1.11.31 - '@swc/core-linux-arm64-musl': 1.11.31 - '@swc/core-linux-x64-gnu': 1.11.31 - '@swc/core-linux-x64-musl': 1.11.31 - '@swc/core-win32-arm64-msvc': 1.11.31 - '@swc/core-win32-ia32-msvc': 1.11.31 - '@swc/core-win32-x64-msvc': 1.11.31 - '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 - '@swc/jest@0.2.38(@swc/core@1.11.31)': + '@tanstack/eslint-plugin-query@5.78.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.11.31 - '@swc/counter': 0.1.3 - jsonc-parser: 3.3.1 + '@typescript-eslint/utils': 8.34.1(eslint@8.57.1)(typescript@5.8.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript - '@swc/types@0.1.22': + '@tanstack/query-core@5.80.7': {} + + '@tanstack/query-devtools@5.80.0': {} + + '@tanstack/react-query-devtools@5.80.10(@tanstack/react-query@5.80.7(react@18.3.1))(react@18.3.1)': dependencies: - '@swc/counter': 0.1.3 + '@tanstack/query-devtools': 5.80.0 + '@tanstack/react-query': 5.80.7(react@18.3.1) + react: 18.3.1 + + '@tanstack/react-query@5.80.7(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.80.7 + react: 18.3.1 '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -10813,7 +9856,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.5.0': + '@testing-library/jest-dom@6.6.3': dependencies: '@adobe/css-tools': 4.4.3 aria-query: 5.3.2 @@ -10823,7 +9866,7 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': dependencies: '@testing-library/dom': 10.4.0 @@ -10908,6 +9951,10 @@ snapshots: '@types/doctrine@0.0.9': {} + '@types/es-aggregate-error@1.0.6': + dependencies: + '@types/node': 22.15.30 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -10920,34 +9967,18 @@ snapshots: '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 '@types/estree@1.0.6': {} - '@types/estree@1.0.7': {} - '@types/estree@1.0.8': {} - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 22.15.30 - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 '@types/html-minifier-terser@6.1.0': {} - '@types/istanbul-lib-coverage@2.0.6': {} - - '@types/istanbul-lib-report@3.0.3': - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - '@types/istanbul-reports@3.0.4': - dependencies: - '@types/istanbul-lib-report': 3.0.3 - '@types/jaro-winkler@0.2.4': {} '@types/json-schema@7.0.15': {} @@ -10956,7 +9987,7 @@ snapshots: '@types/junit-report-builder@3.0.2': {} - '@types/lodash@4.17.17': {} + '@types/lodash@4.17.18': {} '@types/mdast@4.0.4': dependencies: @@ -10990,7 +10021,7 @@ snapshots: '@types/phoenix@1.6.6': {} - '@types/prop-types@15.7.14': {} + '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.5(@types/react@18.3.17)': dependencies: @@ -11002,7 +10033,7 @@ snapshots: '@types/react@18.3.17': dependencies: - '@types/prop-types': 15.7.14 + '@types/prop-types': 15.7.15 csstype: 3.1.3 '@types/resolve@1.20.6': {} @@ -11011,8 +10042,6 @@ snapshots: '@types/shimmer@1.2.0': {} - '@types/stack-utils@2.0.3': {} - '@types/statuses@2.0.6': {} '@types/stylis@4.2.5': {} @@ -11027,30 +10056,20 @@ snapshots: '@types/unist@3.0.3': {} - '@types/uuid@9.0.8': {} - - '@types/wait-on@5.3.4': - dependencies: - '@types/node': 22.15.30 + '@types/urijs@1.19.25': {} '@types/ws@8.18.1': dependencies: '@types/node': 22.15.30 - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@17.0.33': - dependencies: - '@types/yargs-parser': 21.0.3 - - '@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.33.1(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.33.1 + '@typescript-eslint/parser': 8.34.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/type-utils': 8.34.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 eslint: 8.57.1 graphemer: 1.4.0 ignore: 7.0.5 @@ -11060,40 +10079,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.33.1 + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.1 debug: 4.4.1 eslint: 8.57.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.33.1(typescript@5.8.3)': + '@typescript-eslint/project-service@8.34.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.33.1': + '@typescript-eslint/scope-manager@8.34.1': dependencies: - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/visitor-keys': 8.33.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 - '@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.33.1(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.34.1(eslint@8.57.1)(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@8.57.1)(typescript@5.8.3) debug: 4.4.1 eslint: 8.57.1 ts-api-utils: 2.1.0(typescript@5.8.3) @@ -11101,14 +10120,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.33.1': {} + '@typescript-eslint/types@8.34.1': {} - '@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.33.1(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/visitor-keys': 8.33.1 + '@typescript-eslint/project-service': 8.34.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.8.3) + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/visitor-keys': 8.34.1 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -11119,108 +10138,103 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.33.1(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@8.34.1(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.34.1 + '@typescript-eslint/types': 8.34.1 + '@typescript-eslint/typescript-estree': 8.34.1(typescript@5.8.3) eslint: 8.57.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.33.1': + '@typescript-eslint/visitor-keys@8.34.1': dependencies: - '@typescript-eslint/types': 8.33.1 - eslint-visitor-keys: 4.2.0 + '@typescript-eslint/types': 8.34.1 + eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} - '@unrs/resolver-binding-darwin-arm64@1.7.10': + '@unrs/resolver-binding-android-arm-eabi@1.9.0': optional: true - '@unrs/resolver-binding-darwin-x64@1.7.10': + '@unrs/resolver-binding-android-arm64@1.9.0': optional: true - '@unrs/resolver-binding-freebsd-x64@1.7.10': + '@unrs/resolver-binding-darwin-arm64@1.9.0': optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.10': + '@unrs/resolver-binding-darwin-x64@1.9.0': optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.10': + '@unrs/resolver-binding-freebsd-x64@1.9.0': optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.7.10': + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.0': optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.7.10': + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.0': optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.10': + '@unrs/resolver-binding-linux-arm64-gnu@1.9.0': optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.10': + '@unrs/resolver-binding-linux-arm64-musl@1.9.0': optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.7.10': + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.0': optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.7.10': + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.0': optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.7.10': + '@unrs/resolver-binding-linux-riscv64-musl@1.9.0': optional: true - '@unrs/resolver-binding-linux-x64-musl@1.7.10': + '@unrs/resolver-binding-linux-s390x-gnu@1.9.0': optional: true - '@unrs/resolver-binding-wasm32-wasi@1.7.10': + '@unrs/resolver-binding-linux-x64-gnu@1.9.0': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.9.0': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.9.0': dependencies: - '@napi-rs/wasm-runtime': 0.2.10 + '@napi-rs/wasm-runtime': 0.2.11 optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.7.10': + '@unrs/resolver-binding-win32-arm64-msvc@1.9.0': optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.7.10': + '@unrs/resolver-binding-win32-ia32-msvc@1.9.0': optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.7.10': + '@unrs/resolver-binding-win32-x64-msvc@1.9.0': optional: true - '@vitest/expect@2.0.5': + '@vitest/expect@3.0.9': dependencies: - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 + '@vitest/spy': 3.0.9 + '@vitest/utils': 3.0.9 chai: 5.2.0 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/pretty-format@2.0.5': + '@vitest/pretty-format@3.0.9': dependencies: - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/pretty-format@2.1.9': - dependencies: - tinyrainbow: 1.2.0 - - '@vitest/spy@2.0.5': + '@vitest/spy@3.0.9': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.0.5': + '@vitest/utils@3.0.9': dependencies: - '@vitest/pretty-format': 2.0.5 - estree-walker: 3.0.3 - loupe: 3.1.3 - tinyrainbow: 1.2.0 - - '@vitest/utils@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.1.3 - tinyrainbow: 1.2.0 + '@vitest/pretty-format': 3.0.9 + loupe: 3.1.4 + tinyrainbow: 2.0.0 '@webassemblyjs/ast@1.14.1': dependencies: @@ -11302,13 +10316,13 @@ snapshots: '@xtuc/long@4.2.2': {} - '@xyflow/react@12.6.4(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@xyflow/react@12.6.4(@types/react@18.3.17)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@xyflow/system': 0.0.61 classcat: 5.0.5 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.7(@types/react@18.3.17)(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.17)(immer@9.0.21)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer @@ -11331,11 +10345,9 @@ snapshots: dependencies: acorn: 8.15.0 - acorn-jsx@5.3.2(acorn@8.14.1): + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: - acorn: 8.14.1 - - acorn@8.14.1: {} + acorn: 8.15.0 acorn@8.15.0: {} @@ -11350,10 +10362,13 @@ snapshots: transitivePeerDependencies: - supports-color - aggregate-error@3.1.0: + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-errors@3.0.0(ajv@8.17.1): dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 + ajv: 8.17.1 ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: @@ -11382,12 +10397,12 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - ansi-escapes@6.2.1: {} - ansi-html-community@0.0.8: {} ansi-html@0.0.9: {} @@ -11396,10 +10411,6 @@ snapshots: ansi-regex@6.1.0: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -11415,18 +10426,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - append-transform@2.0.0: - dependencies: - default-require-extensions: 3.0.1 - - archy@1.0.0: {} - arg@5.0.2: {} - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - argparse@2.0.1: {} aria-hidden@1.2.6: @@ -11455,6 +10456,8 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 + array-union@2.1.0: {} + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.8 @@ -11528,9 +10531,9 @@ snapshots: dependencies: tslib: 2.8.1 - async-function@1.0.0: {} + astring@1.9.0: {} - asynckit@0.4.0: {} + async-function@1.0.0: {} available-typed-arrays@1.0.7: dependencies: @@ -11543,61 +10546,23 @@ snapshots: axe-core: 4.10.3 mustache: 4.2.0 - axe-playwright@2.1.0(playwright@1.52.0): + axe-playwright@2.1.0(playwright@1.53.1): dependencies: '@types/junit-report-builder': 3.0.2 axe-core: 4.10.3 axe-html-reporter: 2.2.11(axe-core@4.10.3) junit-report-builder: 5.1.1 picocolors: 1.1.1 - playwright: 1.52.0 - - axios@1.9.0: - dependencies: - follow-redirects: 1.15.9 - form-data: 4.0.3 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug + playwright: 1.53.1 axobject-query@4.1.0: {} - babel-jest@29.7.0(@babel/core@7.27.4): - dependencies: - '@babel/core': 7.27.4 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.27.4) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - babel-loader@9.2.1(@babel/core@7.27.4)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + babel-loader@9.2.1(@babel/core@7.27.4)(webpack@5.99.9(esbuild@0.25.5)): dependencies: '@babel/core': 7.27.4 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) - - babel-plugin-istanbul@6.1.1: - dependencies: - '@babel/helper-plugin-utils': 7.27.1 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - babel-plugin-jest-hoist@29.6.3: - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.27.6 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.7 + webpack: 5.99.9(esbuild@0.25.5) babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.27.4): dependencies: @@ -11612,7 +10577,7 @@ snapshots: dependencies: '@babel/core': 7.27.4 '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4) - core-js-compat: 3.42.0 + core-js-compat: 3.43.0 transitivePeerDependencies: - supports-color @@ -11623,31 +10588,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-preset-current-node-syntax@1.1.0(@babel/core@7.27.4): - dependencies: - '@babel/core': 7.27.4 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.27.4) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.27.4) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.27.4) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.27.4) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.4) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.27.4) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.27.4) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.27.4) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.27.4) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.27.4) - - babel-preset-jest@29.6.3(@babel/core@7.27.4): - dependencies: - '@babel/core': 7.27.4 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4) - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -11670,12 +10610,12 @@ snapshots: boring-avatars@1.11.2: {} - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -11685,8 +10625,6 @@ snapshots: brorand@1.1.0: {} - browser-assert@1.2.1: {} - browserify-aes@1.2.0: dependencies: buffer-xor: 1.0.3 @@ -11734,15 +10672,11 @@ snapshots: browserslist@4.25.0: dependencies: - caniuse-lite: 1.0.30001721 - electron-to-chromium: 1.5.165 + caniuse-lite: 1.0.30001723 + electron-to-chromium: 1.5.169 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.0) - bser@2.1.1: - dependencies: - node-int64: 0.4.0 - buffer-from@1.1.2: {} buffer-xor@1.0.3: {} @@ -11758,12 +10692,7 @@ snapshots: dependencies: streamsearch: 1.1.0 - caching-transform@4.0.0: - dependencies: - hasha: 5.2.2 - make-dir: 3.1.0 - package-hash: 4.0.0 - write-file-atomic: 3.0.3 + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: dependencies: @@ -11782,6 +10711,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + call-me-maybe@1.0.2: {} + callsites@3.1.0: {} camel-case@4.1.2: @@ -11791,13 +10722,9 @@ snapshots: camelcase-css@2.0.1: {} - camelcase@5.3.1: {} - - camelcase@6.3.0: {} - camelize@1.0.1: {} - caniuse-lite@1.0.30001721: {} + caniuse-lite@1.0.30001723: {} case-sensitive-paths-webpack-plugin@2.4.0: {} @@ -11808,15 +10735,9 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 + loupe: 3.1.4 pathval: 2.0.0 - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@3.0.0: dependencies: ansi-styles: 4.3.0 @@ -11827,12 +10748,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.4.1: {} - - char-regex@1.0.2: {} - - char-regex@2.0.2: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -11855,11 +10770,15 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chromatic@11.25.2: {} - chrome-trace-event@1.0.4: {} + chromatic@12.2.0: {} - ci-info@3.9.0: {} + chrome-trace-event@1.0.4: {} cipher-base@1.0.6: dependencies: @@ -11878,18 +10797,10 @@ snapshots: dependencies: source-map: 0.6.1 - clean-stack@2.2.0: {} - cli-width@4.1.0: {} client-only@0.0.1: {} - cliui@6.0.0: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -11910,20 +10821,10 @@ snapshots: - '@types/react' - '@types/react-dom' - co@4.6.0: {} - - collect-v8-coverage@1.0.2: {} - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} color-string@1.9.1: @@ -11940,18 +10841,10 @@ snapshots: colorette@2.0.20: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - comma-separated-tokens@2.0.3: {} - commander@12.1.0: {} - commander@2.20.3: {} - commander@3.0.2: {} - commander@4.1.1: {} commander@8.3.0: {} @@ -11960,6 +10853,8 @@ snapshots: commondir@1.0.1: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} concurrently@9.1.2: @@ -11984,11 +10879,11 @@ snapshots: cookie@1.0.2: {} - core-js-compat@3.42.0: + core-js-compat@3.43.0: dependencies: browserslist: 4.25.0 - core-js-pure@3.42.0: {} + core-js-pure@3.43.0: {} core-util-is@1.0.3: {} @@ -12031,21 +10926,6 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@22.15.30): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.15.30) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -12069,18 +10949,18 @@ snapshots: css-color-keywords@1.0.0: {} - css-loader@6.11.0(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + css-loader@6.11.0(webpack@5.99.9(esbuild@0.25.5)): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.4) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.4) - postcss-modules-scope: 3.2.1(postcss@8.5.4) - postcss-modules-values: 4.0.0(postcss@8.5.4) + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) postcss-value-parser: 4.2.0 semver: 7.7.2 optionalDependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) css-select@4.3.0: dependencies: @@ -12104,11 +10984,6 @@ snapshots: csstype@3.1.3: {} - cwd@0.10.0: - dependencies: - find-pkg: 0.1.2 - fs-exists-sync: 0.1.0 - d3-array@3.2.4: dependencies: internmap: 2.0.3 @@ -12205,18 +11080,14 @@ snapshots: dependencies: ms: 2.1.3 - decamelize@1.2.0: {} - decimal.js-light@2.5.1: {} - decode-named-character-reference@1.1.0: + decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 dedent@0.7.0: {} - dedent@1.6.0: {} - deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -12225,10 +11096,6 @@ snapshots: deepmerge@4.3.1: {} - default-require-extensions@3.0.1: - dependencies: - strip-bom: 4.0.0 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -12243,7 +11110,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} + dependency-graph@0.11.0: {} dequal@2.0.3: {} @@ -12255,8 +11122,6 @@ snapshots: detect-libc@2.0.4: optional: true - detect-newline@3.1.0: {} - detect-node-es@1.1.0: {} devlop@1.1.0: @@ -12265,18 +11130,16 @@ snapshots: didyoumean@1.2.2: {} - diff-sequences@29.6.3: {} - - diffable-html@4.1.0: - dependencies: - htmlparser2: 3.10.1 - diffie-hellman@5.0.3: dependencies: bn.js: 4.12.2 miller-rabin: 4.0.1 randombytes: 2.1.0 + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + dlv@1.1.3: {} doctrine@2.1.0: @@ -12300,11 +11163,6 @@ snapshots: '@babel/runtime': 7.27.6 csstype: 3.1.3 - dom-serializer@0.2.2: - dependencies: - domelementtype: 2.3.0 - entities: 2.2.0 - dom-serializer@1.4.1: dependencies: domelementtype: 2.3.0 @@ -12313,23 +11171,12 @@ snapshots: domain-browser@4.23.0: {} - domelementtype@1.3.1: {} - domelementtype@2.3.0: {} - domhandler@2.4.2: - dependencies: - domelementtype: 1.3.1 - domhandler@4.3.1: dependencies: domelementtype: 2.3.0 - domutils@1.7.0: - dependencies: - dom-serializer: 0.2.2 - domelementtype: 1.3.1 - domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -12351,7 +11198,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.165: {} + electron-to-chromium@1.5.169: {} elliptic@6.6.1: dependencies: @@ -12375,8 +11222,6 @@ snapshots: embla-carousel@8.6.0: {} - emittery@0.13.1: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -12394,10 +11239,15 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 - entities@1.1.2: {} + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 entities@2.2.0: {} + entities@4.5.0: {} + env-paths@2.2.1: {} error-ex@1.3.2: @@ -12465,6 +11315,17 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 + es-aggregate-error@1.0.14: + dependencies: + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + set-function-name: 2.0.2 + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -12511,61 +11372,57 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - es6-error@4.1.1: {} + es6-promise@3.3.1: {} - esbuild-register@3.6.0(esbuild@0.24.2): + esbuild-register@3.6.0(esbuild@0.25.5): dependencies: debug: 4.4.1 - esbuild: 0.24.2 + esbuild: 0.25.5 transitivePeerDependencies: - supports-color - esbuild@0.24.2: + esbuild@0.25.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.2 - '@esbuild/android-arm': 0.24.2 - '@esbuild/android-arm64': 0.24.2 - '@esbuild/android-x64': 0.24.2 - '@esbuild/darwin-arm64': 0.24.2 - '@esbuild/darwin-x64': 0.24.2 - '@esbuild/freebsd-arm64': 0.24.2 - '@esbuild/freebsd-x64': 0.24.2 - '@esbuild/linux-arm': 0.24.2 - '@esbuild/linux-arm64': 0.24.2 - '@esbuild/linux-ia32': 0.24.2 - '@esbuild/linux-loong64': 0.24.2 - '@esbuild/linux-mips64el': 0.24.2 - '@esbuild/linux-ppc64': 0.24.2 - '@esbuild/linux-riscv64': 0.24.2 - '@esbuild/linux-s390x': 0.24.2 - '@esbuild/linux-x64': 0.24.2 - '@esbuild/netbsd-arm64': 0.24.2 - '@esbuild/netbsd-x64': 0.24.2 - '@esbuild/openbsd-arm64': 0.24.2 - '@esbuild/openbsd-x64': 0.24.2 - '@esbuild/sunos-x64': 0.24.2 - '@esbuild/win32-arm64': 0.24.2 - '@esbuild/win32-ia32': 0.24.2 - '@esbuild/win32-x64': 0.24.2 + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - - escape-string-regexp@2.0.0: {} - escape-string-regexp@4.0.0: {} - eslint-config-next@15.3.3(eslint@8.57.1)(typescript@5.8.3): + eslint-config-next@15.3.4(eslint@8.57.1)(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.3.3 + '@next/eslint-plugin-next': 15.3.4 '@rushstack/eslint-patch': 1.11.0 - '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) - '@typescript-eslint/parser': 8.33.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.1(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) @@ -12593,24 +11450,24 @@ snapshots: is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.14 - unrs-resolver: 1.7.10 + unrs-resolver: 1.9.0 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.33.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.1(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -12621,7 +11478,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -12633,7 +11490,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.33.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 8.34.1(eslint@8.57.1)(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -12684,12 +11541,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-storybook@0.12.0(eslint@8.57.1)(typescript@5.8.3): + eslint-plugin-storybook@9.0.12(eslint@8.57.1)(storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3))(typescript@5.8.3): dependencies: - '@storybook/csf': 0.1.13 - '@typescript-eslint/utils': 8.33.1(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.1(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 - ts-dedent: 2.2.0 + storybook: 9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3) transitivePeerDependencies: - supports-color - typescript @@ -12706,7 +11562,7 @@ snapshots: eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} + eslint-visitor-keys@4.2.1: {} eslint@8.57.1: dependencies: @@ -12753,8 +11609,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -12775,10 +11631,6 @@ snapshots: estree-walker@2.0.2: {} - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - esutils@2.0.3: {} event-target-shim@5.0.1: {} @@ -12806,22 +11658,6 @@ snapshots: exenv@1.2.2: {} - exit@0.1.2: {} - - expand-tilde@1.2.2: - dependencies: - os-homedir: 1.0.2 - - expect-playwright@0.8.0: {} - - expect@29.7.0: - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - extend@3.0.2: {} fast-deep-equal@2.0.1: {} @@ -12852,17 +11688,17 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-memoize@2.5.2: {} + + fast-safe-stringify@2.1.1: {} + fast-uri@3.0.6: {} fastq@1.19.1: dependencies: reusify: 1.1.0 - fb-watchman@2.0.2: - dependencies: - bser: 2.1.1 - - fdir@6.4.5(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -12889,21 +11725,6 @@ snapshots: common-path-prefix: 3.0.0 pkg-dir: 7.0.0 - find-file-up@0.1.3: - dependencies: - fs-exists-sync: 0.1.0 - resolve-dir: 0.1.1 - - find-pkg@0.1.2: - dependencies: - find-file-up: 0.1.3 - - find-process@1.4.10: - dependencies: - chalk: 4.1.2 - commander: 12.1.0 - loglevel: 1.9.2 - find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -12919,6 +11740,12 @@ snapshots: locate-path: 7.2.0 path-exists: 5.0.0 + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + flat-cache@3.2.0: dependencies: flatted: 3.3.3 @@ -12927,23 +11754,16 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.9: {} - for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@2.0.0: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 3.0.7 - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -12958,38 +11778,32 @@ snapshots: semver: 7.7.2 tapable: 2.2.2 typescript: 5.8.3 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) - - form-data@4.0.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 + webpack: 5.99.9(esbuild@0.25.5) forwarded-parse@2.1.2: {} framer-motion@12.16.0(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - motion-dom: 12.16.0 - motion-utils: 12.12.1 + motion-dom: 12.18.1 + motion-utils: 12.18.1 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.2.2 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - fromentries@1.3.2: {} - - fs-exists-sync@0.1.0: {} - fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-monkey@1.0.6: {} fs.realpath@1.0.0: {} @@ -13013,9 +11827,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.4.2(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + geist@1.4.2(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: - next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) gensync@1.0.0-beta.2: {} @@ -13036,8 +11850,6 @@ snapshots: get-nonce@1.0.1: {} - get-package-type@0.1.0: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -13090,18 +11902,6 @@ snapshots: minipass: 4.2.8 path-scurry: 1.11.1 - global-modules@0.2.3: - dependencies: - global-prefix: 0.1.5 - is-windows: 0.2.0 - - global-prefix@0.1.5: - dependencies: - homedir-polyfill: 1.0.3 - ini: 1.3.8 - is-windows: 0.2.0 - which: 1.3.1 - globals@11.12.0: {} globals@13.24.0: @@ -13113,6 +11913,15 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -13123,8 +11932,6 @@ snapshots: has-bigints@1.1.0: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -13151,18 +11958,13 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 - hasha@5.2.2: - dependencies: - is-stream: 2.0.1 - type-fest: 0.8.1 - hasown@2.0.2: dependencies: function-bind: 1.1.2 hast-util-to-jsx-runtime@2.3.6: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 '@types/hast': 3.0.4 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 @@ -13174,7 +11976,7 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.16 + style-to-js: 1.1.17 unist-util-position: 5.0.0 vfile-message: 4.0.2 transitivePeerDependencies: @@ -13198,14 +12000,8 @@ snapshots: dependencies: react-is: 16.13.1 - homedir-polyfill@1.0.3: - dependencies: - parse-passwd: 1.0.0 - html-entities@2.6.0: {} - html-escaper@2.0.2: {} - html-minifier-terser@6.1.0: dependencies: camel-case: 4.1.2 @@ -13214,11 +12010,11 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.41.0 + terser: 5.42.0 html-url-attributes@3.0.1: {} - html-webpack-plugin@5.6.3(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + html-webpack-plugin@5.6.3(webpack@5.99.9(esbuild@0.25.5)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -13226,16 +12022,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.2 optionalDependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) - - htmlparser2@3.10.1: - dependencies: - domelementtype: 1.3.1 - domhandler: 2.4.2 - domutils: 1.7.0 - entities: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 + webpack: 5.99.9(esbuild@0.25.5) htmlparser2@6.1.0: dependencies: @@ -13244,6 +12031,8 @@ snapshots: domutils: 2.8.0 entities: 2.2.0 + http2-client@1.3.5: {} + https-browserify@1.0.0: {} https-proxy-agent@5.0.1: @@ -13255,9 +12044,9 @@ snapshots: human-signals@2.1.0: {} - icss-utils@5.1.0(postcss@8.5.4): + icss-utils@5.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 ieee754@1.2.1: {} @@ -13265,27 +12054,22 @@ snapshots: ignore@7.0.5: {} - image-size@1.2.1: - dependencies: - queue: 6.0.2 + image-size@2.0.2: {} + + immer@9.0.21: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.14.0: + import-in-the-middle@1.14.2: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 - import-local@3.2.0: - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -13297,8 +12081,6 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} - inline-style-parser@0.2.4: {} internal-slot@1.1.0: @@ -13386,8 +12168,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-fn@2.1.0: {} - is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -13457,8 +12237,6 @@ snapshots: dependencies: which-typed-array: 1.1.19 - is-typedarray@1.0.0: {} - is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -13470,10 +12248,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-windows@0.2.0: {} - - is-windows@1.0.2: {} - is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -13484,69 +12258,6 @@ snapshots: isexe@2.0.0: {} - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-hook@3.0.0: - dependencies: - append-transform: 2.0.0 - - istanbul-lib-instrument@4.0.3: - dependencies: - '@babel/core': 7.27.4 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@5.2.1: - dependencies: - '@babel/core': 7.27.4 - '@babel/parser': 7.27.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.27.4 - '@babel/parser': 7.27.5 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - istanbul-lib-processinfo@2.0.3: - dependencies: - archy: 1.0.0 - cross-spawn: 7.0.6 - istanbul-lib-coverage: 3.2.2 - p-map: 3.0.0 - rimraf: 3.0.2 - uuid: 8.3.2 - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@4.0.1: - dependencies: - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -13564,396 +12275,21 @@ snapshots: jaro-winkler@0.2.8: {} - jest-changed-files@29.7.0: - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - jest-circus@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.6.0 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-cli@29.7.0(@types/node@22.15.30): - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.15.30) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.15.30) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-config@29.7.0(@types/node@22.15.30): - dependencies: - '@babel/core': 7.27.4 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.27.4) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.15.30 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-docblock@29.7.0: - dependencies: - detect-newline: 3.1.0 - - jest-each@29.7.0: - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 - - jest-environment-node@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - jest-get-type@29.6.3: {} - - jest-haste-map@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 22.15.30 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - - jest-junit@16.0.0: - dependencies: - mkdirp: 1.0.4 - strip-ansi: 6.0.1 - uuid: 8.3.2 - xml: 1.0.1 - - jest-leak-detector@29.7.0: - dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-message-util@29.7.0: - dependencies: - '@babel/code-frame': 7.27.1 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - jest-mock@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - jest-util: 29.7.0 - - jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@22.15.30)): - dependencies: - expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@22.15.30) - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-process-manager: 0.4.0 - jest-runner: 29.7.0 - nyc: 15.1.0 - playwright-core: 1.52.0 - rimraf: 3.0.2 - uuid: 8.3.2 - transitivePeerDependencies: - - debug - - supports-color - - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: - jest-resolve: 29.7.0 - - jest-process-manager@0.4.0: - dependencies: - '@types/wait-on': 5.3.4 - chalk: 4.1.2 - cwd: 0.10.0 - exit: 0.1.2 - find-process: 1.4.10 - prompts: 2.4.2 - signal-exit: 3.0.7 - spawnd: 5.0.0 - tree-kill: 1.2.2 - wait-on: 7.2.0 - transitivePeerDependencies: - - debug - - supports-color - - jest-regex-util@29.6.3: {} - - jest-resolve-dependencies@29.7.0: - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - jest-resolve@29.7.0: - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.10 - resolve.exports: 2.0.3 - slash: 3.0.0 - - jest-runner@29.7.0: - dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - - jest-runtime@29.7.0: - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - chalk: 4.1.2 - cjs-module-lexer: 1.4.3 - collect-v8-coverage: 1.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - jest-serializer-html@7.1.0: - dependencies: - diffable-html: 4.1.0 - - jest-snapshot@29.7.0: - dependencies: - '@babel/core': 7.27.4 - '@babel/generator': 7.27.5 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) - '@babel/types': 7.27.6 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.27.4) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - jest-util@29.7.0: - dependencies: - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - jest-validate@29.7.0: - dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 - - jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@22.15.30)): - dependencies: - ansi-escapes: 6.2.1 - chalk: 5.4.1 - jest: 29.7.0(@types/node@22.15.30) - jest-regex-util: 29.6.3 - jest-watcher: 29.7.0 - slash: 5.1.0 - string-length: 5.0.1 - strip-ansi: 7.1.0 - - jest-watcher@29.7.0: - dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.15.30 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 - jest-worker@27.5.1: dependencies: '@types/node': 22.15.30 merge-stream: 2.0.0 supports-color: 8.1.1 - jest-worker@29.7.0: - dependencies: - '@types/node': 22.15.30 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - jest@29.7.0(@types/node@22.15.30): - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.15.30) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jiti@1.21.7: {} - joi@17.13.3: - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.5 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - js-tokens@4.0.0: {} - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - js-yaml@4.1.0: dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@4.1.0: {} + jsep@1.4.0: {} jsesc@3.0.2: {} @@ -13975,7 +12311,7 @@ snapshots: json5@2.2.3: {} - jsonc-parser@3.3.1: {} + jsonc-parser@2.2.1: {} jsonfile@6.1.0: dependencies: @@ -13983,6 +12319,16 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonpath-plus@10.3.0: + dependencies: + '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + jsep: 1.4.0 + + jsonpointer@5.0.1: {} + + jsonschema@1.5.0: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -14000,8 +12346,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kleur@3.0.3: {} - language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -14038,6 +12382,10 @@ snapshots: lines-and-columns@1.2.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + loader-runner@4.3.0: {} loader-utils@2.0.4: @@ -14064,12 +12412,24 @@ snapshots: lodash.debounce@4.0.8: {} - lodash.flattendeep@4.4.0: {} + lodash.isempty@4.4.0: {} lodash.merge@4.6.2: {} + lodash.omitby@4.6.0: {} + + lodash.topath@4.5.2: {} + + lodash.uniq@4.5.0: {} + + lodash.uniqby@4.7.0: {} + + lodash.uniqwith@4.5.0: {} + lodash@4.17.21: {} + loglevel-plugin-prefix@0.8.4: {} + loglevel@1.9.2: {} longest-streak@3.1.0: {} @@ -14078,7 +12438,7 @@ snapshots: dependencies: js-tokens: 4.0.0 - loupe@3.1.3: {} + loupe@3.1.4: {} lower-case@2.0.2: dependencies: @@ -14094,6 +12454,8 @@ snapshots: dependencies: react: 18.3.1 + lunr@2.3.9: {} + lz-string@1.5.0: {} magic-string@0.30.17: @@ -14108,15 +12470,14 @@ snapshots: dependencies: semver: 6.3.1 - make-dir@4.0.0: + markdown-it@14.1.0: dependencies: - semver: 7.7.2 - - makeerror@1.0.12: - dependencies: - tmpl: 1.0.5 - - map-or-similar@1.5.0: {} + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 math-intrinsics@1.1.0: {} @@ -14130,7 +12491,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.1.0 + decode-named-character-reference: 1.2.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -14215,21 +12576,19 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdurl@2.0.0: {} + memfs@3.5.3: dependencies: fs-monkey: 1.0.6 - memoizerific@1.11.3: - dependencies: - map-or-similar: 1.5.0 - merge-stream@2.0.0: {} merge2@1.4.1: {} micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.1.0 + decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -14304,7 +12663,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.1.0 + decode-named-character-reference: 1.2.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -14342,7 +12701,7 @@ snapshots: dependencies: '@types/debug': 4.1.12 debug: 4.4.1 - decode-named-character-reference: 1.1.0 + decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -14386,15 +12745,19 @@ snapshots: minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 + + minimatch@6.2.0: + dependencies: + brace-expansion: 2.0.2 minimatch@8.0.4: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} @@ -14402,17 +12765,15 @@ snapshots: minipass@7.1.2: {} - mkdirp@1.0.4: {} - module-details-from-path@1.0.4: {} moment@2.30.1: {} - motion-dom@12.16.0: + motion-dom@12.18.1: dependencies: - motion-utils: 12.12.1 + motion-utils: 12.18.1 - motion-utils@12.12.1: {} + motion-utils@12.18.1: {} ms@2.1.3: {} @@ -14469,13 +12830,13 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.53.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.3.3 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001721 + caniuse-lite: 1.0.30001723 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -14490,12 +12851,22 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.3.3 '@next/swc-win32-x64-msvc': 15.3.3 '@opentelemetry/api': 1.9.0 - '@playwright/test': 1.52.0 + '@playwright/test': 1.53.1 sharp: 0.34.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + nimma@0.2.3: + dependencies: + '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) + '@jsep-plugin/ternary': 1.1.4(jsep@1.4.0) + astring: 1.9.0 + jsep: 1.4.0 + optionalDependencies: + jsonpath-plus: 10.3.0 + lodash.topath: 4.5.2 + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -14503,13 +12874,15 @@ snapshots: node-abort-controller@3.1.1: {} + node-fetch-h2@2.3.0: + dependencies: + http2-client: 1.3.5 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 - node-int64@0.4.0: {} - - node-polyfill-webpack-plugin@2.0.1(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.99.9(esbuild@0.25.5)): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -14536,11 +12909,11 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) - node-preload@0.2.1: + node-readfiles@0.2.0: dependencies: - process-on-spawn: 1.1.0 + es6-promise: 3.3.1 node-releases@2.0.19: {} @@ -14554,37 +12927,36 @@ snapshots: dependencies: boolbase: 1.0.0 - nyc@15.1.0: + oas-kit-common@1.0.8: dependencies: - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - caching-transform: 4.0.0 - convert-source-map: 1.9.0 - decamelize: 1.2.0 - find-cache-dir: 3.3.2 - find-up: 4.1.0 - foreground-child: 2.0.0 - get-package-type: 0.1.0 - glob: 7.2.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-hook: 3.0.0 - istanbul-lib-instrument: 4.0.3 - istanbul-lib-processinfo: 2.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - make-dir: 3.1.0 - node-preload: 0.2.1 - p-map: 3.0.0 - process-on-spawn: 1.1.0 - resolve-from: 5.0.0 - rimraf: 3.0.2 - signal-exit: 3.0.7 - spawn-wrap: 2.0.0 - test-exclude: 6.0.0 - yargs: 15.4.1 - transitivePeerDependencies: - - supports-color + fast-safe-stringify: 2.1.1 + + oas-linter@3.2.2: + dependencies: + '@exodus/schemasafe': 1.3.0 + should: 13.2.3 + yaml: 1.10.2 + + oas-resolver@2.5.6: + dependencies: + node-fetch-h2: 2.3.0 + oas-kit-common: 1.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 + + oas-schema-walker@1.1.5: {} + + oas-validator@5.0.8: + dependencies: + call-me-maybe: 1.0.2 + oas-kit-common: 1.0.8 + oas-linter: 3.2.2 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + reftools: 1.1.9 + should: 13.2.3 + yaml: 1.10.2 object-assign@4.1.1: {} @@ -14651,6 +13023,16 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + openapi-types@12.1.3: {} + + openapi3-ts@4.2.2: + dependencies: + yaml: 2.8.0 + + openapi3-ts@4.4.0: + dependencies: + yaml: 2.8.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -14660,9 +13042,40 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - os-browserify@0.3.0: {} + orval@7.10.0(openapi-types@12.1.3): + dependencies: + '@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3) + '@orval/angular': 7.10.0(openapi-types@12.1.3) + '@orval/axios': 7.10.0(openapi-types@12.1.3) + '@orval/core': 7.10.0(openapi-types@12.1.3) + '@orval/fetch': 7.10.0(openapi-types@12.1.3) + '@orval/hono': 7.10.0(openapi-types@12.1.3) + '@orval/mcp': 7.10.0(openapi-types@12.1.3) + '@orval/mock': 7.10.0(openapi-types@12.1.3) + '@orval/query': 7.10.0(openapi-types@12.1.3) + '@orval/swr': 7.10.0(openapi-types@12.1.3) + '@orval/zod': 7.10.0(openapi-types@12.1.3) + ajv: 8.17.1 + cac: 6.7.14 + chalk: 4.1.2 + chokidar: 4.0.3 + enquirer: 2.4.1 + execa: 5.1.1 + find-up: 5.0.0 + fs-extra: 11.3.0 + lodash.uniq: 4.5.0 + openapi3-ts: 4.2.2 + string-argv: 0.3.2 + tsconfck: 2.1.2(typescript@5.8.3) + typedoc: 0.28.5(typescript@5.8.3) + typedoc-plugin-markdown: 4.6.4(typedoc@0.28.5(typescript@5.8.3)) + typescript: 5.8.3 + transitivePeerDependencies: + - encoding + - openapi-types + - supports-color - os-homedir@1.0.2: {} + os-browserify@0.3.0: {} outvariant@1.4.3: {} @@ -14696,19 +13109,8 @@ snapshots: dependencies: p-limit: 4.0.0 - p-map@3.0.0: - dependencies: - aggregate-error: 3.1.0 - p-try@2.2.0: {} - package-hash@4.0.0: - dependencies: - graceful-fs: 4.2.11 - hasha: 5.2.2 - lodash.flattendeep: 4.4.0 - release-zalgo: 1.0.0 - package-json-from-dist@1.0.1: {} pako@1.0.11: {} @@ -14736,7 +13138,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.1.0 + decode-named-character-reference: 1.2.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -14748,8 +13150,6 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse-passwd@1.0.0: {} - party-js@2.2.0: {} pascal-case@3.1.2: @@ -14818,80 +13218,72 @@ snapshots: dependencies: find-up: 6.3.0 - playwright-core@1.52.0: {} + playwright-core@1.53.1: {} - playwright@1.52.0: + playwright@1.53.1: dependencies: - playwright-core: 1.52.0 + playwright-core: 1.53.1 optionalDependencies: fsevents: 2.3.2 - pnp-webpack-plugin@1.7.0(typescript@5.8.3): - dependencies: - ts-pnp: 1.2.0(typescript@5.8.3) - transitivePeerDependencies: - - typescript - - polished@4.3.1: - dependencies: - '@babel/runtime': 7.27.6 + pony-cause@1.1.1: {} possible-typed-array-names@1.1.0: {} - postcss-import@15.1.0(postcss@8.5.4): + postcss-import@15.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.10 - postcss-js@4.0.1(postcss@8.5.4): + postcss-js@4.0.1(postcss@8.5.6): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.4 + postcss: 8.5.6 - postcss-load-config@4.0.2(postcss@8.5.4): + postcss-load-config@4.0.2(postcss@8.5.6): dependencies: lilconfig: 3.1.3 yaml: 2.8.0 optionalDependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-loader@8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@5.99.9(esbuild@0.25.5)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 - postcss: 8.5.4 + postcss: 8.5.6 semver: 7.7.2 optionalDependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) transitivePeerDependencies: - typescript - postcss-modules-extract-imports@3.1.0(postcss@8.5.4): + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 - postcss-modules-local-by-default@4.2.0(postcss@8.5.4): + postcss-modules-local-by-default@4.2.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.4): + postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 7.1.0 - postcss-modules-values@4.0.0(postcss@8.5.4): + postcss-modules-values@4.0.0(postcss@8.5.6): dependencies: - icss-utils: 5.1.0(postcss@8.5.4) - postcss: 8.5.4 + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 - postcss-nested@6.2.0(postcss@8.5.4): + postcss-nested@6.2.0(postcss@8.5.6): dependencies: - postcss: 8.5.4 + postcss: 8.5.6 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -14918,7 +13310,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.4: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -14953,27 +13345,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - process-nextick-args@2.0.1: {} - process-on-spawn@1.1.0: - dependencies: - fromentries: 1.3.2 - process@0.11.10: {} progress@2.0.3: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -14997,12 +13374,12 @@ snapshots: randombytes: 2.1.0 safe-buffer: 5.2.1 + punycode.js@2.3.1: {} + punycode@1.4.1: {} punycode@2.3.1: {} - pure-rand@6.1.0: {} - qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -15013,10 +13390,6 @@ snapshots: queue-microtask@1.2.3: {} - queue@6.0.2: - dependencies: - inherits: 2.0.4 - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -15028,11 +13401,6 @@ snapshots: range-parser@1.2.1: {} - react-confetti@6.4.0(react@18.3.1): - dependencies: - react: 18.3.1 - tween-functions: 1.2.0 - react-day-picker@9.7.0(react@18.3.1): dependencies: '@date-fns/tz': 1.2.0 @@ -15040,7 +13408,7 @@ snapshots: date-fns-jalali: 4.1.0-0 react: 18.3.1 - react-docgen-typescript@2.2.2(typescript@5.8.3): + react-docgen-typescript@2.4.0(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -15070,7 +13438,7 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-components: 6.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + styled-components: 6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-hook-form@7.57.0(react@18.3.1): dependencies: @@ -15203,6 +13571,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + recast@0.23.11: dependencies: ast-types: 0.16.1 @@ -15244,6 +13614,8 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + reftools@1.1.9: {} + regenerate-unicode-properties@10.2.0: dependencies: regenerate: 1.4.2 @@ -15278,10 +13650,6 @@ snapshots: relateurl@0.2.7: {} - release-zalgo@1.0.0: - dependencies: - es6-error: 4.1.1 - remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -15319,23 +13687,10 @@ snapshots: transitivePeerDependencies: - supports-color - require-main-filename@2.0.0: {} - requires-port@1.0.0: {} - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - - resolve-dir@0.1.1: - dependencies: - expand-tilde: 1.2.2 - global-modules: 0.2.3 - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} resolve-url-loader@5.0.0: @@ -15343,11 +13698,9 @@ snapshots: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.4 + postcss: 8.5.6 source-map: 0.6.1 - resolve.exports@2.0.3: {} - resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -15433,11 +13786,13 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - sass-loader@14.2.1(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + safe-stable-stringify@1.1.1: {} + + sass-loader@14.2.1(webpack@5.99.9(esbuild@0.25.5)): dependencies: neo-async: 2.6.2 optionalDependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) scheduler@0.23.2: dependencies: @@ -15464,8 +13819,6 @@ snapshots: dependencies: randombytes: 2.1.0 - set-blocking@2.0.0: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -15497,33 +13850,6 @@ snapshots: shallowequal@1.1.0: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.0.4 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - optional: true - sharp@0.34.2: dependencies: color: 4.2.3 @@ -15569,6 +13895,32 @@ snapshots: shimmer@1.2.1: {} + should-equal@2.0.0: + dependencies: + should-type: 1.4.0 + + should-format@3.0.3: + dependencies: + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + + should-type-adaptors@1.1.0: + dependencies: + should-type: 1.4.0 + should-util: 1.0.1 + + should-type@1.4.0: {} + + should-util@1.0.1: {} + + should@13.2.3: + dependencies: + should-equal: 2.0.0 + should-format: 3.0.3 + should-type: 1.4.0 + should-type-adaptors: 1.1.0 + should-util: 1.0.1 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -15601,24 +13953,19 @@ snapshots: signal-exit@4.1.0: {} + simple-eval@1.0.1: + dependencies: + jsep: 1.4.0 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 optional: true - sisteransi@1.0.5: {} - slash@3.0.0: {} - slash@5.1.0: {} - source-map-js@1.2.1: {} - source-map-support@0.5.13: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -15630,32 +13977,8 @@ snapshots: space-separated-tokens@2.0.2: {} - spawn-wrap@2.0.0: - dependencies: - foreground-child: 2.0.0 - is-windows: 1.0.2 - make-dir: 3.1.0 - rimraf: 3.0.2 - signal-exit: 3.0.7 - which: 2.0.2 - - spawnd@5.0.0: - dependencies: - exit: 0.1.2 - signal-exit: 3.0.7 - tree-kill: 1.2.2 - wait-port: 0.2.14 - transitivePeerDependencies: - - supports-color - - sprintf-js@1.0.3: {} - stable-hash@0.0.5: {} - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - stackframe@1.3.4: {} stacktrace-parser@0.1.11: @@ -15669,12 +13992,23 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@8.6.14(prettier@3.5.3): + storybook@9.0.12(@testing-library/dom@10.4.0)(prettier@3.5.3): dependencies: - '@storybook/core': 8.6.14(prettier@3.5.3)(storybook@8.6.14(prettier@3.5.3)) + '@storybook/global': 5.0.0 + '@testing-library/jest-dom': 6.6.3 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) + '@vitest/expect': 3.0.9 + '@vitest/spy': 3.0.9 + better-opn: 3.0.2 + esbuild: 0.25.5 + esbuild-register: 3.6.0(esbuild@0.25.5) + recast: 0.23.11 + semver: 7.7.2 + ws: 8.18.2 optionalDependencies: prettier: 3.5.3 transitivePeerDependencies: + - '@testing-library/dom' - bufferutil - supports-color - utf-8-validate @@ -15695,15 +14029,7 @@ snapshots: strict-event-emitter@0.5.1: {} - string-length@4.0.2: - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - - string-length@5.0.1: - dependencies: - char-regex: 2.0.2 - strip-ansi: 7.1.0 + string-argv@0.3.2: {} string-width@4.2.3: dependencies: @@ -15790,8 +14116,6 @@ snapshots: strip-bom@3.0.0: {} - strip-bom@4.0.0: {} - strip-final-newline@2.0.0: {} strip-indent@3.0.0: @@ -15804,19 +14128,19 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@3.3.4(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + style-loader@3.3.4(webpack@5.99.9(esbuild@0.25.5)): dependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) - style-to-js@1.1.16: + style-to-js@1.1.17: dependencies: - style-to-object: 1.0.8 + style-to-object: 1.0.9 - style-to-object@1.0.8: + style-to-object@1.0.9: dependencies: inline-style-parser: 0.2.4 - styled-components@6.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -15856,10 +14180,6 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -15870,6 +14190,22 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swagger2openapi@7.0.8: + dependencies: + call-me-maybe: 1.0.2 + node-fetch: 2.7.0 + node-fetch-h2: 2.3.0 + node-readfiles: 0.2.0 + oas-kit-common: 1.0.8 + oas-resolver: 2.5.6 + oas-schema-walker: 1.1.5 + oas-validator: 5.0.8 + reftools: 1.1.9 + yaml: 1.10.2 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + tailwind-merge@2.6.0: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.17): @@ -15892,11 +14228,11 @@ snapshots: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.4 - postcss-import: 15.1.0(postcss@8.5.4) - postcss-js: 4.0.1(postcss@8.5.4) - postcss-load-config: 4.0.2(postcss@8.5.4) - postcss-nested: 6.2.0(postcss@8.5.4) + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.0.1(postcss@8.5.6) + postcss-load-config: 4.0.2(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.10 sucrase: 3.35.0 @@ -15905,31 +14241,24 @@ snapshots: tapable@2.2.2: {} - terser-webpack-plugin@5.3.14(@swc/core@1.11.31)(esbuild@0.24.2)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + terser-webpack-plugin@5.3.14(esbuild@0.25.5)(webpack@5.99.9(esbuild@0.25.5)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - terser: 5.41.0 - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + terser: 5.42.0 + webpack: 5.99.9(esbuild@0.25.5) optionalDependencies: - '@swc/core': 1.11.31 - esbuild: 0.24.2 + esbuild: 0.25.5 - terser@5.41.0: + terser@5.42.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@6.0.0: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - text-table@0.2.0: {} thenify-all@1.6.0: @@ -15950,15 +14279,13 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.5(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 - tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} tinyspy@3.0.2: {} - tmpl@1.0.5: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -15986,7 +14313,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-pnp@1.2.0(typescript@5.8.3): + tsconfck@2.1.2(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -16010,28 +14337,24 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@1.14.1: {} + tslib@2.6.2: {} tslib@2.8.1: {} tty-browserify@0.0.1: {} - tween-functions@1.2.0: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.20.2: {} type-fest@0.21.3: {} type-fest@0.7.1: {} - type-fest@0.8.1: {} - type-fest@2.19.0: {} type-fest@4.41.0: {} @@ -16069,12 +14392,23 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typedarray-to-buffer@3.1.5: + typedoc-plugin-markdown@4.6.4(typedoc@0.28.5(typescript@5.8.3)): dependencies: - is-typedarray: 1.0.0 + typedoc: 0.28.5(typescript@5.8.3) + + typedoc@0.28.5(typescript@5.8.3): + dependencies: + '@gerrit0/mini-shiki': 3.6.0 + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + typescript: 5.8.3 + yaml: 2.8.0 typescript@5.8.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -16095,6 +14429,8 @@ snapshots: unicode-property-aliases-ecmascript@2.1.0: {} + unicorn-magic@0.1.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -16141,30 +14477,32 @@ snapshots: unplugin@1.16.1: dependencies: - acorn: 8.14.1 + acorn: 8.15.0 webpack-virtual-modules: 0.6.2 - unrs-resolver@1.7.10: + unrs-resolver@1.9.0: dependencies: napi-postinstall: 0.2.4 optionalDependencies: - '@unrs/resolver-binding-darwin-arm64': 1.7.10 - '@unrs/resolver-binding-darwin-x64': 1.7.10 - '@unrs/resolver-binding-freebsd-x64': 1.7.10 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.7.10 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.7.10 - '@unrs/resolver-binding-linux-arm64-gnu': 1.7.10 - '@unrs/resolver-binding-linux-arm64-musl': 1.7.10 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.7.10 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.7.10 - '@unrs/resolver-binding-linux-riscv64-musl': 1.7.10 - '@unrs/resolver-binding-linux-s390x-gnu': 1.7.10 - '@unrs/resolver-binding-linux-x64-gnu': 1.7.10 - '@unrs/resolver-binding-linux-x64-musl': 1.7.10 - '@unrs/resolver-binding-wasm32-wasi': 1.7.10 - '@unrs/resolver-binding-win32-arm64-msvc': 1.7.10 - '@unrs/resolver-binding-win32-ia32-msvc': 1.7.10 - '@unrs/resolver-binding-win32-x64-msvc': 1.7.10 + '@unrs/resolver-binding-android-arm-eabi': 1.9.0 + '@unrs/resolver-binding-android-arm64': 1.9.0 + '@unrs/resolver-binding-darwin-arm64': 1.9.0 + '@unrs/resolver-binding-darwin-x64': 1.9.0 + '@unrs/resolver-binding-freebsd-x64': 1.9.0 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.9.0 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.9.0 + '@unrs/resolver-binding-linux-arm64-gnu': 1.9.0 + '@unrs/resolver-binding-linux-arm64-musl': 1.9.0 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.9.0 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.9.0 + '@unrs/resolver-binding-linux-riscv64-musl': 1.9.0 + '@unrs/resolver-binding-linux-s390x-gnu': 1.9.0 + '@unrs/resolver-binding-linux-x64-gnu': 1.9.0 + '@unrs/resolver-binding-linux-x64-musl': 1.9.0 + '@unrs/resolver-binding-wasm32-wasi': 1.9.0 + '@unrs/resolver-binding-win32-arm64-msvc': 1.9.0 + '@unrs/resolver-binding-win32-ia32-msvc': 1.9.0 + '@unrs/resolver-binding-win32-x64-msvc': 1.9.0 update-browserslist-db@1.1.3(browserslist@4.25.0): dependencies: @@ -16176,6 +14514,8 @@ snapshots: dependencies: punycode: 2.3.1 + urijs@1.19.11: {} + url-parse@1.5.10: dependencies: querystringify: 2.2.0 @@ -16217,17 +14557,15 @@ snapshots: utila@0.4.0: {} + utility-types@3.11.0: {} + uuid@11.1.0: {} uuid@8.3.2: {} uuid@9.0.1: {} - v8-to-istanbul@9.3.0: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 + validator@13.15.15: {} vfile-message@4.0.2: dependencies: @@ -16258,28 +14596,6 @@ snapshots: vm-browserify@1.1.2: {} - wait-on@7.2.0: - dependencies: - axios: 1.9.0 - joi: 17.13.3 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.2 - transitivePeerDependencies: - - debug - - wait-port@0.2.14: - dependencies: - chalk: 2.4.2 - commander: 3.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - walker@1.0.8: - dependencies: - makeerror: 1.0.12 - warning@4.0.3: dependencies: loose-envify: 1.4.0 @@ -16291,7 +14607,7 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-dev-middleware@6.1.3(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)): + webpack-dev-middleware@6.1.3(webpack@5.99.9(esbuild@0.25.5)): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -16299,7 +14615,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.99.9(@swc/core@1.11.31)(esbuild@0.24.2) + webpack: 5.99.9(esbuild@0.25.5) webpack-hot-middleware@2.26.1: dependencies: @@ -16313,7 +14629,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2): + webpack@5.99.9(esbuild@0.25.5): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -16321,7 +14637,7 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.1 + acorn: 8.15.0 browserslist: 4.25.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.1 @@ -16336,7 +14652,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.2 tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(@swc/core@1.11.31)(esbuild@0.24.2)(webpack@5.99.9(@swc/core@1.11.31)(esbuild@0.24.2)) + terser-webpack-plugin: 5.3.14(esbuild@0.25.5)(webpack@5.99.9(esbuild@0.25.5)) watchpack: 2.4.4 webpack-sources: 3.3.2 transitivePeerDependencies: @@ -16380,8 +14696,6 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-module@2.0.1: {} - which-typed-array@1.1.19: dependencies: available-typed-arrays: 1.0.7 @@ -16392,10 +14706,6 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 - which@1.3.1: - dependencies: - isexe: 2.0.0 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -16422,28 +14732,12 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@3.0.3: - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - - write-file-atomic@4.0.2: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - ws@8.18.2: {} - xml@1.0.1: {} - xmlbuilder@15.1.1: {} xtend@4.0.2: {} - y18n@4.0.3: {} - y18n@5.0.8: {} yallist@3.1.1: {} @@ -16452,27 +14746,8 @@ snapshots: yaml@2.8.0: {} - yargs-parser@18.1.3: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - yargs-parser@21.1.1: {} - yargs@15.4.1: - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - yargs@17.7.2: dependencies: cliui: 8.0.1 @@ -16491,11 +14766,12 @@ snapshots: zod@3.25.56: {} - zustand@4.5.7(@types/react@18.3.17)(react@18.3.1): + zustand@4.5.7(@types/react@18.3.17)(immer@9.0.21)(react@18.3.1): dependencies: use-sync-external-store: 1.5.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.17 + immer: 9.0.21 react: 18.3.1 zwitch@2.0.4: {} diff --git a/autogpt_platform/frontend/src/api/mutators/custom-mutator.ts b/autogpt_platform/frontend/src/api/mutators/custom-mutator.ts new file mode 100644 index 0000000000..dc7dd1065c --- /dev/null +++ b/autogpt_platform/frontend/src/api/mutators/custom-mutator.ts @@ -0,0 +1,81 @@ +import { getSupabaseClient } from "@/lib/supabase/getSupabaseClient"; + +const BASE_URL = + process.env.NEXT_PUBLIC_AGPT_SERVER_BASE_URL || "http://localhost:8006"; + +const getBody = <T>(c: Response | Request): Promise<T> => { + const contentType = c.headers.get("content-type"); + + if (contentType && contentType.includes("application/json")) { + return c.json(); + } + + if (contentType && contentType.includes("application/pdf")) { + return c.blob() as Promise<T>; + } + + return c.text() as Promise<T>; +}; + +const getSupabaseToken = async () => { + const supabase = await getSupabaseClient(); + + const { + data: { session }, + } = (await supabase?.auth.getSession()) || { + data: { session: null }, + }; + + return session?.access_token; +}; + +export const customMutator = async <T = any>( + url: string, + options: RequestInit & { + params?: any; + } = {}, +): Promise<T> => { + const { params, ...requestOptions } = options; + const method = (requestOptions.method || "GET") as + | "GET" + | "POST" + | "PUT" + | "DELETE" + | "PATCH"; + const data = requestOptions.body; + const headers: Record<string, string> = { + ...((requestOptions.headers as Record<string, string>) || {}), + }; + + const token = await getSupabaseToken(); + + if (token) { + headers["Authorization"] = `Bearer ${token}`; + } + + const isFormData = data instanceof FormData; + + // Currently, only two content types are handled here: application/json and multipart/form-data + if (!isFormData && data && !headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + + const queryString = params + ? "?" + new URLSearchParams(params).toString() + : ""; + + const response = await fetch(`${BASE_URL}${url}${queryString}`, { + ...requestOptions, + method, + headers, + body: data, + }); + + const response_data = await getBody<T>(response); + + return { + status: response.status, + response_data, + headers: response.headers, + } as T; +}; diff --git a/autogpt_platform/frontend/src/api/openapi.json b/autogpt_platform/frontend/src/api/openapi.json new file mode 100644 index 0000000000..40a88952b1 --- /dev/null +++ b/autogpt_platform/frontend/src/api/openapi.json @@ -0,0 +1,6093 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "AutoGPT Agent Server", + "summary": "AutoGPT Agent Server", + "description": "This server is used to execute agents that are created by the AutoGPT system.", + "version": "0.1" + }, + "paths": { + "/api/integrations/{provider}/login": { + "get": { + "tags": ["v1", "integrations"], + "summary": "Login", + "operationId": "getV1Login", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The provider to initiate an OAuth flow for" + } + }, + { + "name": "scopes", + "in": "query", + "required": false, + "schema": { + "type": "string", + "title": "Comma-separated list of authorization scopes", + "default": "" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LoginResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/integrations/{provider}/callback": { + "post": { + "tags": ["v1", "integrations"], + "summary": "Callback", + "operationId": "postV1Callback", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The target provider for this OAuth exchange" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Body_postV1Callback" } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CredentialsMetaResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/integrations/credentials": { + "get": { + "tags": ["v1", "integrations"], + "summary": "List Credentials", + "operationId": "getV1ListCredentials", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/CredentialsMetaResponse" + }, + "type": "array", + "title": "Response Getv1Listcredentials" + } + } + } + } + } + } + }, + "/api/integrations/{provider}/credentials": { + "get": { + "tags": ["v1", "integrations"], + "summary": "List Credentials By Provider", + "operationId": "getV1ListCredentialsByProvider", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The provider to list credentials for" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CredentialsMetaResponse" + }, + "title": "Response Getv1Listcredentialsbyprovider" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "post": { + "tags": ["v1", "integrations"], + "summary": "Create Credentials", + "operationId": "postV1CreateCredentials", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The provider to create credentials for" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { "$ref": "#/components/schemas/OAuth2Credentials" }, + { "$ref": "#/components/schemas/APIKeyCredentials" }, + { "$ref": "#/components/schemas/UserPasswordCredentials" } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "oauth2": "#/components/schemas/OAuth2Credentials", + "api_key": "#/components/schemas/APIKeyCredentials", + "user_password": "#/components/schemas/UserPasswordCredentials" + } + }, + "title": "Credentials" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { "$ref": "#/components/schemas/OAuth2Credentials" }, + { "$ref": "#/components/schemas/APIKeyCredentials" }, + { "$ref": "#/components/schemas/UserPasswordCredentials" } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "oauth2": "#/components/schemas/OAuth2Credentials", + "api_key": "#/components/schemas/APIKeyCredentials", + "user_password": "#/components/schemas/UserPasswordCredentials" + } + }, + "title": "Response Postv1Createcredentials" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/integrations/{provider}/credentials/{cred_id}": { + "get": { + "tags": ["v1", "integrations"], + "summary": "Get Credential", + "operationId": "getV1GetCredential", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The provider to retrieve credentials for" + } + }, + { + "name": "cred_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "The ID of the credentials to retrieve" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { "$ref": "#/components/schemas/OAuth2Credentials" }, + { "$ref": "#/components/schemas/APIKeyCredentials" }, + { "$ref": "#/components/schemas/UserPasswordCredentials" } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "oauth2": "#/components/schemas/OAuth2Credentials", + "api_key": "#/components/schemas/APIKeyCredentials", + "user_password": "#/components/schemas/UserPasswordCredentials" + } + }, + "title": "Response Getv1Getcredential" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "delete": { + "tags": ["v1", "integrations"], + "summary": "Delete Credentials", + "operationId": "deleteV1DeleteCredentials", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "The provider to delete credentials for" + } + }, + { + "name": "cred_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "The ID of the credentials to delete" + } + }, + { + "name": "force", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "title": "Whether to proceed if any linked webhooks are still in use", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/CredentialsDeletionResponse" + }, + { + "$ref": "#/components/schemas/CredentialsDeletionNeedsConfirmationResponse" + } + ], + "title": "Response Deletev1Deletecredentials" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/integrations/{provider}/webhooks/{webhook_id}/ingress": { + "post": { + "tags": ["v1", "integrations"], + "summary": "Webhook Ingress Generic", + "operationId": "postV1WebhookIngressGeneric", + "parameters": [ + { + "name": "provider", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/ProviderName", + "title": "Provider where the webhook was registered" + } + }, + { + "name": "webhook_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Our ID for the webhook" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/integrations/webhooks/{webhook_id}/ping": { + "post": { + "tags": ["v1", "integrations"], + "summary": "Webhook Ping", + "operationId": "postV1WebhookPing", + "parameters": [ + { + "name": "webhook_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Our ID for the webhook" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/analytics/log_raw_metric": { + "post": { + "tags": ["v1", "analytics"], + "summary": "Log Raw Metric", + "operationId": "postV1LogRawMetric", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV1LogRawMetric" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/analytics/log_raw_analytics": { + "post": { + "tags": ["v1", "analytics"], + "summary": "Log Raw Analytics", + "operationId": "postV1LogRawAnalytics", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV1LogRawAnalytics" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/auth/user": { + "post": { + "tags": ["v1", "auth"], + "summary": "Get or create user", + "operationId": "postV1Get or create user", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + }, + "/api/auth/user/email": { + "post": { + "tags": ["v1", "auth"], + "summary": "Update user email", + "operationId": "postV1Update user email", + "requestBody": { + "content": { + "application/json": { + "schema": { "type": "string", "title": "Email" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Response Postv1Update User Email" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/auth/user/preferences": { + "get": { + "tags": ["v1", "auth"], + "summary": "Get notification preferences", + "operationId": "getV1Get notification preferences", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationPreference" + } + } + } + } + } + }, + "post": { + "tags": ["v1", "auth"], + "summary": "Update notification preferences", + "operationId": "postV1Update notification preferences", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationPreferenceDTO" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationPreference" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/onboarding": { + "get": { + "tags": ["v1", "onboarding"], + "summary": "Get onboarding status", + "operationId": "getV1Get onboarding status", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + }, + "patch": { + "tags": ["v1", "onboarding"], + "summary": "Update onboarding progress", + "operationId": "patchV1Update onboarding progress", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserOnboardingUpdate" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/onboarding/agents": { + "get": { + "tags": ["v1", "onboarding"], + "summary": "Get recommended agents", + "operationId": "getV1Get recommended agents", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + }, + "/api/onboarding/enabled": { + "get": { + "tags": ["v1", "onboarding", "public"], + "summary": "Check onboarding enabled", + "operationId": "getV1Check onboarding enabled", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + }, + "/api/blocks": { + "get": { + "tags": ["v1", "blocks"], + "summary": "List available blocks", + "operationId": "getV1List available blocks", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { "additionalProperties": true, "type": "object" }, + "type": "array", + "title": "Response Getv1List Available Blocks" + } + } + } + } + } + } + }, + "/api/blocks/{block_id}/execute": { + "post": { + "tags": ["v1", "blocks"], + "summary": "Execute graph block", + "operationId": "postV1Execute graph block", + "parameters": [ + { + "name": "block_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Block Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Data" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { "type": "array", "items": {} }, + "title": "Response Postv1Execute Graph Block" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits": { + "get": { + "tags": ["v1", "credits"], + "summary": "Get user credits", + "operationId": "getV1Get user credits", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { "type": "integer" }, + "type": "object", + "title": "Response Getv1Get User Credits" + } + } + } + } + } + }, + "post": { + "tags": ["v1", "credits"], + "summary": "Request credit top up", + "operationId": "postV1Request credit top up", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/RequestTopUp" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "patch": { + "tags": ["v1", "credits"], + "summary": "Fulfill checkout session", + "operationId": "patchV1Fulfill checkout session", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + }, + "/api/credits/{transaction_key}/refund": { + "post": { + "tags": ["v1", "credits"], + "summary": "Refund credit transaction", + "operationId": "postV1Refund credit transaction", + "parameters": [ + { + "name": "transaction_key", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Transaction Key" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { "type": "string" }, + "title": "Metadata" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Response Postv1Refund Credit Transaction" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits/auto-top-up": { + "get": { + "tags": ["v1", "credits"], + "summary": "Get auto top up", + "operationId": "getV1Get auto top up", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AutoTopUpConfig" } + } + } + } + } + }, + "post": { + "tags": ["v1", "credits"], + "summary": "Configure auto top up", + "operationId": "postV1Configure auto top up", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/AutoTopUpConfig" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "string", + "title": "Response Postv1Configure Auto Top Up" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits/stripe_webhook": { + "post": { + "tags": ["v1", "credits"], + "summary": "Handle Stripe webhooks", + "operationId": "postV1Handle stripe webhooks", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + }, + "/api/credits/manage": { + "get": { + "tags": ["v1", "credits"], + "summary": "Manage payment methods", + "operationId": "getV1Manage payment methods", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Response Getv1Manage Payment Methods" + } + } + } + } + } + } + }, + "/api/credits/transactions": { + "get": { + "tags": ["v1", "credits"], + "summary": "Get credit history", + "operationId": "getV1Get credit history", + "parameters": [ + { + "name": "transaction_time", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Transaction Time" + } + }, + { + "name": "transaction_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Transaction Type" + } + }, + { + "name": "transaction_count_limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 100, + "title": "Transaction Count Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TransactionHistory" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits/refunds": { + "get": { + "tags": ["v1", "credits"], + "summary": "Get refund requests", + "operationId": "getV1Get refund requests", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { "$ref": "#/components/schemas/RefundRequest" }, + "type": "array", + "title": "Response Getv1Get Refund Requests" + } + } + } + } + } + } + }, + "/api/graphs": { + "get": { + "tags": ["v1", "graphs"], + "summary": "List user graphs", + "operationId": "getV1List user graphs", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { "$ref": "#/components/schemas/GraphModel" }, + "type": "array", + "title": "Response Getv1List User Graphs" + } + } + } + } + } + }, + "post": { + "tags": ["v1", "graphs"], + "summary": "Create new graph", + "operationId": "postV1Create new graph", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CreateGraph" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GraphModel" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/versions/{version}": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get graph version", + "operationId": "getV1Get graph version", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Version" + } + }, + { + "name": "for_export", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "For Export" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GraphModel" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get specific graph", + "operationId": "getV1Get specific graph", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "version", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Version" + } + }, + { + "name": "for_export", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "For Export" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GraphModel" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "delete": { + "tags": ["v1", "graphs"], + "summary": "Delete graph permanently", + "operationId": "deleteV1Delete graph permanently", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/DeleteGraphResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "put": { + "tags": ["v1", "graphs"], + "summary": "Update graph version", + "operationId": "putV1Update graph version", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Graph" } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GraphModel" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/versions": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get all graph versions", + "operationId": "getV1Get all graph versions", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { "$ref": "#/components/schemas/GraphModel" }, + "title": "Response Getv1Get All Graph Versions" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/versions/active": { + "put": { + "tags": ["v1", "graphs"], + "summary": "Set active graph version", + "operationId": "putV1Set active graph version", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/SetGraphActiveVersion" } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/execute/{graph_version}": { + "post": { + "tags": ["v1", "graphs"], + "summary": "Execute graph agent", + "operationId": "postV1Execute graph agent", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "graph_version", + "in": "path", + "required": true, + "schema": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Graph Version" + } + }, + { + "name": "preset_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Preset Id" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV1Execute_graph_agent" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteGraphResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/executions/{graph_exec_id}/stop": { + "post": { + "tags": ["v1", "graphs"], + "summary": "Stop graph execution", + "operationId": "postV1Stop graph execution", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "graph_exec_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Exec Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/GraphExecution" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/executions": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get all executions", + "operationId": "getV1Get all executions", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/GraphExecutionMeta" + }, + "type": "array", + "title": "Response Getv1Get All Executions" + } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/executions": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get graph executions", + "operationId": "getV1Get graph executions", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphExecutionMeta" + }, + "title": "Response Getv1Get Graph Executions" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/graphs/{graph_id}/executions/{graph_exec_id}": { + "get": { + "tags": ["v1", "graphs"], + "summary": "Get execution details", + "operationId": "getV1Get execution details", + "parameters": [ + { + "name": "graph_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "graph_exec_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Exec Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { "$ref": "#/components/schemas/GraphExecution" }, + { "$ref": "#/components/schemas/GraphExecutionWithNodes" } + ], + "title": "Response Getv1Get Execution Details" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/executions/{graph_exec_id}": { + "delete": { + "tags": ["v1", "graphs"], + "summary": "Delete graph execution", + "operationId": "deleteV1Delete graph execution", + "parameters": [ + { + "name": "graph_exec_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Graph Exec Id" } + } + ], + "responses": { + "204": { "description": "Successful Response" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/schedules": { + "post": { + "tags": ["v1", "schedules"], + "summary": "Create execution schedule", + "operationId": "postV1Create execution schedule", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduleCreationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GraphExecutionJobInfo" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "get": { + "tags": ["v1", "schedules"], + "summary": "List execution schedules", + "operationId": "getV1List execution schedules", + "parameters": [ + { + "name": "graph_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Graph Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphExecutionJobInfo" + }, + "title": "Response Getv1List Execution Schedules" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/schedules/{schedule_id}": { + "delete": { + "tags": ["v1", "schedules"], + "summary": "Delete execution schedule", + "operationId": "deleteV1Delete execution schedule", + "parameters": [ + { + "name": "schedule_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Schedule Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Deletev1Delete Execution Schedule" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/api-keys": { + "get": { + "tags": ["v1", "api-keys"], + "summary": "List user API keys", + "description": "List all API keys for the user", + "operationId": "getV1List user api keys", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/APIKeyWithoutHash" + }, + "type": "array" + }, + { + "additionalProperties": { "type": "string" }, + "type": "object" + } + ], + "title": "Response Getv1List User Api Keys" + } + } + } + } + } + }, + "post": { + "tags": ["v1", "api-keys"], + "summary": "Create new API key", + "description": "Create a new API key", + "operationId": "postV1Create new api key", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CreateAPIKeyRequest" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAPIKeyResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/api-keys/{key_id}": { + "get": { + "tags": ["v1", "api-keys"], + "summary": "Get specific API key", + "description": "Get a specific API key", + "operationId": "getV1Get specific api key", + "parameters": [ + { + "name": "key_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Key Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/APIKeyWithoutHash" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "delete": { + "tags": ["v1", "api-keys"], + "summary": "Revoke API key", + "description": "Revoke an API key", + "operationId": "deleteV1Revoke api key", + "parameters": [ + { + "name": "key_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Key Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/APIKeyWithoutHash" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/api-keys/{key_id}/suspend": { + "post": { + "tags": ["v1", "api-keys"], + "summary": "Suspend API key", + "description": "Suspend an API key", + "operationId": "postV1Suspend api key", + "parameters": [ + { + "name": "key_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Key Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/APIKeyWithoutHash" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/api-keys/{key_id}/permissions": { + "put": { + "tags": ["v1", "api-keys"], + "summary": "Update key permissions", + "description": "Update API key permissions", + "operationId": "putV1Update key permissions", + "parameters": [ + { + "name": "key_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Key Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePermissionsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/APIKeyWithoutHash" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/profile": { + "get": { + "tags": ["v2", "store", "private"], + "summary": "Get user profile", + "description": "Get the profile details for the authenticated user.", + "operationId": "getV2Get user profile", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProfileDetails" } + } + } + } + } + }, + "post": { + "tags": ["v2", "store", "private"], + "summary": "Update user profile", + "description": "Update the store profile for the authenticated user.\n\nArgs:\n profile (Profile): The updated profile details\n user_id (str): ID of the authenticated user\n\nReturns:\n CreatorDetails: The updated profile\n\nRaises:\n HTTPException: If there is an error updating the profile", + "operationId": "postV2Update user profile", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Profile" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CreatorDetails" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/agents": { + "get": { + "tags": ["v2", "store", "public"], + "summary": "List store agents", + "description": "Get a paginated list of agents from the store with optional filtering and sorting.\n\nArgs:\n featured (bool, optional): Filter to only show featured agents. Defaults to False.\n creator (str | None, optional): Filter agents by creator username. Defaults to None.\n sorted_by (str | None, optional): Sort agents by \"runs\" or \"rating\". Defaults to None.\n search_query (str | None, optional): Search agents by name, subheading and description. Defaults to None.\n category (str | None, optional): Filter agents by category. Defaults to None.\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of agents per page. Defaults to 20.\n\nReturns:\n StoreAgentsResponse: Paginated list of agents matching the filters\n\nRaises:\n HTTPException: If page or page_size are less than 1\n\nUsed for:\n- Home Page Featured Agents\n- Home Page Top Agents\n- Search Results\n- Agent Details - Other Agents By Creator\n- Agent Details - Similar Agents\n- Creator Details - Agents By Creator", + "operationId": "getV2List store agents", + "parameters": [ + { + "name": "featured", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Featured" + } + }, + { + "name": "creator", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Creator" + } + }, + { + "name": "sorted_by", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Sorted By" + } + }, + { + "name": "search_query", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Search Query" + } + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Category" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 1, "title": "Page" } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 20, "title": "Page Size" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreAgentsResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/agents/{username}/{agent_name}": { + "get": { + "tags": ["v2", "store", "public"], + "summary": "Get specific agent", + "description": "This is only used on the AgentDetails Page\n\nIt returns the store listing agents details.", + "operationId": "getV2Get specific agent", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Username" } + }, + { + "name": "agent_name", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Agent Name" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreAgentDetails" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/graph/{store_listing_version_id}": { + "get": { + "tags": ["v2", "store"], + "summary": "Get agent graph", + "description": "Get Agent Graph from Store Listing Version ID.", + "operationId": "getV2Get agent graph", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Store Listing Version Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/agents/{store_listing_version_id}": { + "get": { + "tags": ["v2", "store"], + "summary": "Get agent by version", + "description": "Get Store Agent Details from Store Listing Version ID.", + "operationId": "getV2Get agent by version", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Store Listing Version Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreAgentDetails" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/agents/{username}/{agent_name}/review": { + "post": { + "tags": ["v2", "store"], + "summary": "Create agent review", + "description": "Create a review for a store agent.\n\nArgs:\n username: Creator's username\n agent_name: Name/slug of the agent\n review: Review details including score and optional comments\n user_id: ID of authenticated user creating the review\n\nReturns:\n The created review", + "operationId": "postV2Create agent review", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Username" } + }, + { + "name": "agent_name", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Agent Name" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreReviewCreate" } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreReview" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/creators": { + "get": { + "tags": ["v2", "store", "public"], + "summary": "List store creators", + "description": "This is needed for:\n- Home Page Featured Creators\n- Search Results Page\n\n---\n\nTo support this functionality we need:\n- featured: bool - to limit the list to just featured agents\n- search_query: str - vector search based on the creators profile description.\n- sorted_by: [agent_rating, agent_runs] -", + "operationId": "getV2List store creators", + "parameters": [ + { + "name": "featured", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Featured" + } + }, + { + "name": "search_query", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Search Query" + } + }, + { + "name": "sorted_by", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Sorted By" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 1, "title": "Page" } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 20, "title": "Page Size" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CreatorsResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/creator/{username}": { + "get": { + "tags": ["v2", "store", "public"], + "summary": "Get creator details", + "description": "Get the details of a creator\n- Creator Details Page", + "operationId": "getV2Get creator details", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Username" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/CreatorDetails" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/myagents": { + "get": { + "tags": ["v2", "store", "private"], + "summary": "Get my agents", + "operationId": "getV2Get my agents", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/MyAgentsResponse" } + } + } + } + } + } + }, + "/api/store/submissions/{submission_id}": { + "delete": { + "tags": ["v2", "store", "private"], + "summary": "Delete store submission", + "description": "Delete a store listing submission.\n\nArgs:\n user_id (str): ID of the authenticated user\n submission_id (str): ID of the submission to be deleted\n\nReturns:\n bool: True if the submission was successfully deleted, False otherwise", + "operationId": "deleteV2Delete store submission", + "parameters": [ + { + "name": "submission_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Submission Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "boolean", + "title": "Response Deletev2Delete Store Submission" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/submissions": { + "get": { + "tags": ["v2", "store", "private"], + "summary": "List my submissions", + "description": "Get a paginated list of store submissions for the authenticated user.\n\nArgs:\n user_id (str): ID of the authenticated user\n page (int, optional): Page number for pagination. Defaults to 1.\n page_size (int, optional): Number of submissions per page. Defaults to 20.\n\nReturns:\n StoreListingsResponse: Paginated list of store submissions\n\nRaises:\n HTTPException: If page or page_size are less than 1", + "operationId": "getV2List my submissions", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 1, "title": "Page" } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 20, "title": "Page Size" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoreSubmissionsResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "post": { + "tags": ["v2", "store", "private"], + "summary": "Create store submission", + "description": "Create a new store listing submission.\n\nArgs:\n submission_request (StoreSubmissionRequest): The submission details\n user_id (str): ID of the authenticated user submitting the listing\n\nReturns:\n StoreSubmission: The created store submission\n\nRaises:\n HTTPException: If there is an error creating the submission", + "operationId": "postV2Create store submission", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoreSubmissionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreSubmission" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/submissions/media": { + "post": { + "tags": ["v2", "store", "private"], + "summary": "Upload submission media", + "description": "Upload media (images/videos) for a store listing submission.\n\nArgs:\n file (UploadFile): The media file to upload\n user_id (str): ID of the authenticated user uploading the media\n\nReturns:\n str: URL of the uploaded media file\n\nRaises:\n HTTPException: If there is an error uploading the media", + "operationId": "postV2Upload submission media", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_postV2Upload_submission_media" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/submissions/generate_image": { + "post": { + "tags": ["v2", "store", "private"], + "summary": "Generate submission image", + "description": "Generate an image for a store listing submission.\n\nArgs:\n agent_id (str): ID of the agent to generate an image for\n user_id (str): ID of the authenticated user\n\nReturns:\n JSONResponse: JSON containing the URL of the generated image", + "operationId": "postV2Generate submission image", + "parameters": [ + { + "name": "agent_id", + "in": "query", + "required": true, + "schema": { "type": "string", "title": "Agent Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/download/agents/{store_listing_version_id}": { + "get": { + "tags": ["v2", "store", "public"], + "summary": "Download agent file", + "description": "Download the agent file by streaming its content.\n\nArgs:\n store_listing_version_id (str): The ID of the agent to download\n\nReturns:\n StreamingResponse: A streaming response containing the agent's graph data.\n\nRaises:\n HTTPException: If the agent is not found or an unexpected error occurs.", + "operationId": "getV2Download agent file", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "The ID of the agent to download", + "title": "Store Listing Version Id" + }, + "description": "The ID of the agent to download" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/admin/listings": { + "get": { + "tags": ["v2", "admin", "store", "admin"], + "summary": "Get Admin Listings History", + "description": "Get store listings with their version history for admins.\n\nThis provides a consolidated view of listings with their versions,\nallowing for an expandable UI in the admin dashboard.\n\nArgs:\n status: Filter by submission status (PENDING, APPROVED, REJECTED)\n search: Search by name, description, or user email\n page: Page number for pagination\n page_size: Number of items per page\n\nReturns:\n StoreListingsWithVersionsResponse with listings and their versions", + "operationId": "getV2Get admin listings history", + "parameters": [ + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { "$ref": "#/components/schemas/SubmissionStatus" }, + { "type": "null" } + ], + "title": "Status" + } + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Search" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 1, "title": "Page" } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 20, "title": "Page Size" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoreListingsWithVersionsResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/admin/submissions/{store_listing_version_id}/review": { + "post": { + "tags": ["v2", "admin", "store", "admin"], + "summary": "Review Store Submission", + "description": "Review a store listing submission.\n\nArgs:\n store_listing_version_id: ID of the submission to review\n request: Review details including approval status and comments\n user: Authenticated admin user performing the review\n\nReturns:\n StoreSubmission with updated review information", + "operationId": "postV2Review store submission", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Store Listing Version Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewSubmissionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/StoreSubmission" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/store/admin/submissions/download/{store_listing_version_id}": { + "get": { + "tags": ["v2", "admin", "store", "admin", "store", "admin"], + "summary": "Admin Download Agent File", + "description": "Download the agent file by streaming its content.\n\nArgs:\n store_listing_version_id (str): The ID of the agent to download\n\nReturns:\n StreamingResponse: A streaming response containing the agent's graph data.\n\nRaises:\n HTTPException: If the agent is not found or an unexpected error occurs.", + "operationId": "getV2Admin download agent file", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "The ID of the agent to download", + "title": "Store Listing Version Id" + }, + "description": "The ID of the agent to download" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits/admin/add_credits": { + "post": { + "tags": ["v2", "admin", "credits", "admin"], + "summary": "Add Credits to User", + "operationId": "postV2Add credits to user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV2Add_credits_to_user" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddUserCreditsResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/credits/admin/users_history": { + "get": { + "tags": ["v2", "admin", "credits", "admin"], + "summary": "Get All Users History", + "operationId": "getV2Get all users history", + "parameters": [ + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Search" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 1, "title": "Page" } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { "type": "integer", "default": 20, "title": "Page Size" } + }, + { + "name": "transaction_filter", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { "$ref": "#/components/schemas/CreditTransactionType" }, + { "type": "null" } + ], + "title": "Transaction Filter" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserHistoryResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/presets": { + "get": { + "tags": ["v2", "presets"], + "summary": "List presets", + "description": "Retrieve a paginated list of presets for the current user.", + "operationId": "getV2List presets", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 10, + "title": "Page Size" + } + }, + { + "name": "graph_id", + "in": "query", + "required": true, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "description": "Allows to filter presets by a specific agent graph", + "title": "Graph Id" + }, + "description": "Allows to filter presets by a specific agent graph" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryAgentPresetResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "post": { + "tags": ["v2", "presets"], + "summary": "Create a new preset", + "description": "Create a new preset for the current user.", + "operationId": "postV2Create a new preset", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/LibraryAgentPresetCreatable" + }, + { + "$ref": "#/components/schemas/LibraryAgentPresetCreatableFromGraphExecution" + } + ], + "title": "Preset" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgentPreset" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/presets/{preset_id}": { + "get": { + "tags": ["v2", "presets"], + "summary": "Get a specific preset", + "description": "Retrieve details for a specific preset by its ID.", + "operationId": "getV2Get a specific preset", + "parameters": [ + { + "name": "preset_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Preset Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgentPreset" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "patch": { + "tags": ["v2", "presets"], + "summary": "Update an existing preset", + "description": "Update an existing preset by its ID.", + "operationId": "patchV2Update an existing preset", + "parameters": [ + { + "name": "preset_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Preset Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryAgentPresetUpdatable" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgentPreset" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "delete": { + "tags": ["v2", "presets"], + "summary": "Delete a preset", + "description": "Delete an existing preset by its ID.", + "operationId": "deleteV2Delete a preset", + "parameters": [ + { + "name": "preset_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Preset Id" } + } + ], + "responses": { + "204": { "description": "Successful Response" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/presets/{preset_id}/execute": { + "post": { + "tags": ["v2", "presets", "presets"], + "summary": "Execute a preset", + "description": "Execute a preset with the given graph and node input for the current user.", + "operationId": "postV2Execute a preset", + "parameters": [ + { + "name": "preset_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Preset Id" } + }, + { + "name": "graph_id", + "in": "query", + "required": true, + "schema": { "type": "string", "title": "Graph Id" } + }, + { + "name": "graph_version", + "in": "query", + "required": true, + "schema": { "type": "integer", "title": "Graph Version" } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV2Execute_a_preset" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Postv2Execute A Preset" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/agents": { + "get": { + "tags": ["v2", "library", "private"], + "summary": "List Library Agents", + "description": "Get all agents in the user's library (both created and saved).\n\nArgs:\n user_id: ID of the authenticated user.\n search_term: Optional search term to filter agents by name/description.\n filter_by: List of filters to apply (favorites, created by user).\n sort_by: List of sorting criteria (created date, updated date).\n page: Page number to retrieve.\n page_size: Number of agents per page.\n\nReturns:\n A LibraryAgentResponse containing agents and pagination metadata.\n\nRaises:\n HTTPException: If a server/database error occurs.", + "operationId": "getV2List library agents", + "parameters": [ + { + "name": "search_term", + "in": "query", + "required": false, + "schema": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "description": "Search term to filter agents", + "title": "Search Term" + }, + "description": "Search term to filter agents" + }, + { + "name": "sort_by", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/LibraryAgentSort", + "description": "Criteria to sort results by", + "default": "updatedAt" + }, + "description": "Criteria to sort results by" + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number to retrieve (must be >= 1)", + "default": 1, + "title": "Page" + }, + "description": "Page number to retrieve (must be >= 1)" + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Number of agents per page (must be >= 1)", + "default": 15, + "title": "Page Size" + }, + "description": "Number of agents per page (must be >= 1)" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryAgentResponse" + } + } + } + }, + "500": { + "description": "Server error", + "content": { "application/json": {} } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "post": { + "tags": ["v2", "library", "private"], + "summary": "Add Marketplace Agent", + "description": "Add an agent from the marketplace to the user's library.\n\nArgs:\n store_listing_version_id: ID of the store listing version to add.\n user_id: ID of the authenticated user.\n\nReturns:\n library_model.LibraryAgent: Agent added to the library\n\nRaises:\n HTTPException(404): If the listing version is not found.\n HTTPException(500): If a server/database error occurs.", + "operationId": "postV2Add marketplace agent", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_postV2Add_marketplace_agent" + } + } + } + }, + "responses": { + "201": { + "description": "Agent added successfully", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgent" } + } + } + }, + "404": { "description": "Store listing version not found" }, + "500": { "description": "Server error" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/agents/{library_agent_id}": { + "get": { + "tags": ["v2", "library", "private"], + "summary": "Get Library Agent", + "operationId": "getV2Get library agent", + "parameters": [ + { + "name": "library_agent_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Library Agent Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgent" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + }, + "put": { + "tags": ["v2", "library", "private"], + "summary": "Update Library Agent", + "description": "Update the library agent with the given fields.\n\nArgs:\n library_agent_id: ID of the library agent to update.\n payload: Fields to update (auto_update_version, is_favorite, etc.).\n user_id: ID of the authenticated user.\n\nReturns:\n 204 (No Content) on success.\n\nRaises:\n HTTPException(500): If a server/database error occurs.", + "operationId": "putV2Update library agent", + "parameters": [ + { + "name": "library_agent_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Library Agent Id" } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryAgentUpdateRequest" + } + } + } + }, + "responses": { + "204": { "description": "Agent updated successfully" }, + "500": { "description": "Server error" }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/agents/marketplace/{store_listing_version_id}": { + "get": { + "tags": ["v2", "library", "private", "store, library"], + "summary": "Get Agent By Store ID", + "description": "Get Library Agent from Store Listing Version ID.", + "operationId": "getV2Get agent by store id", + "parameters": [ + { + "name": "store_listing_version_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Store Listing Version Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { "$ref": "#/components/schemas/LibraryAgent" }, + { "type": "null" } + ], + "title": "Response Getv2Get Agent By Store Id" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/library/agents/{library_agent_id}/fork": { + "post": { + "tags": ["v2", "library", "private"], + "summary": "Fork Library Agent", + "operationId": "postV2Fork library agent", + "parameters": [ + { + "name": "library_agent_id", + "in": "path", + "required": true, + "schema": { "type": "string", "title": "Library Agent Id" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/LibraryAgent" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/otto/ask": { + "post": { + "tags": ["v2", "otto"], + "summary": "Proxy Otto Chat Request", + "description": "Proxy requests to Otto API while adding necessary security headers and logging.\nRequires an authenticated user.", + "operationId": "postV2Proxy otto chat request", + "requestBody": { + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ChatRequest" } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ApiResponse" } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/turnstile/verify": { + "post": { + "tags": ["v2", "turnstile"], + "summary": "Verify Turnstile Token", + "description": "Verify a Cloudflare Turnstile token.\nThis endpoint verifies a token returned by the Cloudflare Turnstile challenge\non the client side. It returns whether the verification was successful.", + "operationId": "postV2Verify turnstile token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TurnstileVerifyRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TurnstileVerifyResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/email/unsubscribe": { + "post": { + "tags": ["v1", "email"], + "summary": "One Click Email Unsubscribe", + "operationId": "postV1One click email unsubscribe", + "parameters": [ + { + "name": "token", + "in": "query", + "required": true, + "schema": { "type": "string", "title": "Token" } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + } + } + }, + "/api/email/": { + "post": { + "tags": ["v1", "email"], + "summary": "Handle Postmark Email Webhooks", + "operationId": "postV1Handle postmark email webhooks", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { "$ref": "#/components/schemas/PostmarkDeliveryWebhook" }, + { "$ref": "#/components/schemas/PostmarkBounceWebhook" }, + { + "$ref": "#/components/schemas/PostmarkSpamComplaintWebhook" + }, + { "$ref": "#/components/schemas/PostmarkOpenWebhook" }, + { "$ref": "#/components/schemas/PostmarkClickWebhook" }, + { + "$ref": "#/components/schemas/PostmarkSubscriptionChangeWebhook" + } + ], + "title": "Webhook", + "discriminator": { + "propertyName": "RecordType", + "mapping": { + "Delivery": "#/components/schemas/PostmarkDeliveryWebhook", + "Bounce": "#/components/schemas/PostmarkBounceWebhook", + "SpamComplaint": "#/components/schemas/PostmarkSpamComplaintWebhook", + "Open": "#/components/schemas/PostmarkOpenWebhook", + "Click": "#/components/schemas/PostmarkClickWebhook", + "SubscriptionChange": "#/components/schemas/PostmarkSubscriptionChangeWebhook" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HTTPValidationError" } + } + } + } + }, + "security": [{ "APIKeyHeader": [] }] + } + }, + "/health": { + "get": { + "tags": ["health"], + "summary": "Health", + "operationId": "getHealthHealth", + "responses": { + "200": { + "description": "Successful Response", + "content": { "application/json": { "schema": {} } } + } + } + } + } + }, + "components": { + "schemas": { + "APIKeyCredentials": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "provider": { "type": "string", "title": "Provider" }, + "title": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Title" + }, + "type": { + "type": "string", + "const": "api_key", + "title": "Type", + "default": "api_key" + }, + "api_key": { + "type": "string", + "format": "password", + "title": "Api Key", + "writeOnly": true + }, + "expires_at": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Expires At", + "description": "Unix timestamp (seconds) indicating when the API key expires (if at all)" + } + }, + "type": "object", + "required": ["provider", "api_key"], + "title": "APIKeyCredentials" + }, + "APIKeyPermission": { + "type": "string", + "enum": ["EXECUTE_GRAPH", "READ_GRAPH", "EXECUTE_BLOCK", "READ_BLOCK"], + "title": "APIKeyPermission" + }, + "APIKeyStatus": { + "type": "string", + "enum": ["ACTIVE", "REVOKED", "SUSPENDED"], + "title": "APIKeyStatus" + }, + "APIKeyWithoutHash": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "name": { "type": "string", "title": "Name" }, + "prefix": { "type": "string", "title": "Prefix" }, + "postfix": { "type": "string", "title": "Postfix" }, + "status": { "$ref": "#/components/schemas/APIKeyStatus" }, + "permissions": { + "items": { "$ref": "#/components/schemas/APIKeyPermission" }, + "type": "array", + "title": "Permissions" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "last_used_at": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Last Used At" + }, + "revoked_at": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Revoked At" + }, + "description": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Description" + }, + "user_id": { "type": "string", "title": "User Id" } + }, + "type": "object", + "required": [ + "id", + "name", + "prefix", + "postfix", + "status", + "permissions", + "created_at", + "last_used_at", + "revoked_at", + "description", + "user_id" + ], + "title": "APIKeyWithoutHash" + }, + "AddUserCreditsResponse": { + "properties": { + "new_balance": { "type": "integer", "title": "New Balance" }, + "transaction_key": { "type": "string", "title": "Transaction Key" } + }, + "type": "object", + "required": ["new_balance", "transaction_key"], + "title": "AddUserCreditsResponse" + }, + "AgentExecutionStatus": { + "type": "string", + "enum": [ + "INCOMPLETE", + "QUEUED", + "RUNNING", + "COMPLETED", + "TERMINATED", + "FAILED" + ], + "title": "AgentExecutionStatus" + }, + "ApiResponse": { + "properties": { + "answer": { "type": "string", "title": "Answer" }, + "documents": { + "items": { "$ref": "#/components/schemas/Document" }, + "type": "array", + "title": "Documents" + }, + "success": { "type": "boolean", "title": "Success" } + }, + "type": "object", + "required": ["answer", "documents", "success"], + "title": "ApiResponse" + }, + "AutoTopUpConfig": { + "properties": { + "amount": { "type": "integer", "title": "Amount" }, + "threshold": { "type": "integer", "title": "Threshold" } + }, + "type": "object", + "required": ["amount", "threshold"], + "title": "AutoTopUpConfig" + }, + "BaseGraph-Input": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + } + }, + "type": "object", + "required": ["name", "description"], + "title": "BaseGraph" + }, + "BaseGraph-Output": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema", + "readOnly": true + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema", + "readOnly": true + } + }, + "type": "object", + "required": ["name", "description", "input_schema", "output_schema"], + "title": "BaseGraph" + }, + "Body_postV1Callback": { + "properties": { + "code": { + "type": "string", + "title": "Authorization code acquired by user login" + }, + "state_token": { "type": "string", "title": "Anti-CSRF nonce" } + }, + "type": "object", + "required": ["code", "state_token"], + "title": "Body_postV1Callback" + }, + "Body_postV1Execute_graph_agent": { + "properties": { + "inputs": { + "additionalProperties": true, + "type": "object", + "title": "Inputs" + }, + "credentials_inputs": { + "additionalProperties": { + "$ref": "#/components/schemas/CredentialsMetaInput" + }, + "type": "object", + "title": "Credentials Inputs" + } + }, + "type": "object", + "title": "Body_postV1Execute graph agent" + }, + "Body_postV1LogRawAnalytics": { + "properties": { + "type": { "type": "string", "title": "Type" }, + "data": { + "additionalProperties": true, + "type": "object", + "title": "Data", + "description": "The data to log" + }, + "data_index": { + "type": "string", + "title": "Data Index", + "description": "Indexable field for any count based analytical measures like page order clicking, tutorial step completion, etc." + } + }, + "type": "object", + "required": ["type", "data", "data_index"], + "title": "Body_postV1LogRawAnalytics" + }, + "Body_postV1LogRawMetric": { + "properties": { + "metric_name": { "type": "string", "title": "Metric Name" }, + "metric_value": { "type": "number", "title": "Metric Value" }, + "data_string": { "type": "string", "title": "Data String" } + }, + "type": "object", + "required": ["metric_name", "metric_value", "data_string"], + "title": "Body_postV1LogRawMetric" + }, + "Body_postV2Add_credits_to_user": { + "properties": { + "user_id": { "type": "string", "title": "User Id" }, + "amount": { "type": "integer", "title": "Amount" }, + "comments": { "type": "string", "title": "Comments" } + }, + "type": "object", + "required": ["user_id", "amount", "comments"], + "title": "Body_postV2Add credits to user" + }, + "Body_postV2Add_marketplace_agent": { + "properties": { + "store_listing_version_id": { + "type": "string", + "title": "Store Listing Version Id" + } + }, + "type": "object", + "required": ["store_listing_version_id"], + "title": "Body_postV2Add marketplace agent" + }, + "Body_postV2Execute_a_preset": { + "properties": { + "node_input": { + "additionalProperties": true, + "type": "object", + "title": "Node Input" + } + }, + "type": "object", + "title": "Body_postV2Execute a preset" + }, + "Body_postV2Upload_submission_media": { + "properties": { + "file": { "type": "string", "format": "binary", "title": "File" } + }, + "type": "object", + "required": ["file"], + "title": "Body_postV2Upload submission media" + }, + "ChatRequest": { + "properties": { + "query": { "type": "string", "title": "Query" }, + "conversation_history": { + "items": { "$ref": "#/components/schemas/Message" }, + "type": "array", + "title": "Conversation History" + }, + "message_id": { "type": "string", "title": "Message Id" }, + "include_graph_data": { + "type": "boolean", + "title": "Include Graph Data", + "default": false + }, + "graph_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Graph Id" + } + }, + "type": "object", + "required": ["query", "conversation_history", "message_id"], + "title": "ChatRequest" + }, + "CreateAPIKeyRequest": { + "properties": { + "name": { "type": "string", "title": "Name" }, + "permissions": { + "items": { "$ref": "#/components/schemas/APIKeyPermission" }, + "type": "array", + "title": "Permissions" + }, + "description": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Description" + } + }, + "type": "object", + "required": ["name", "permissions"], + "title": "CreateAPIKeyRequest" + }, + "CreateAPIKeyResponse": { + "properties": { + "api_key": { "$ref": "#/components/schemas/APIKeyWithoutHash" }, + "plain_text_key": { "type": "string", "title": "Plain Text Key" } + }, + "type": "object", + "required": ["api_key", "plain_text_key"], + "title": "CreateAPIKeyResponse" + }, + "CreateGraph": { + "properties": { "graph": { "$ref": "#/components/schemas/Graph" } }, + "type": "object", + "required": ["graph"], + "title": "CreateGraph" + }, + "Creator": { + "properties": { + "name": { "type": "string", "title": "Name" }, + "username": { "type": "string", "title": "Username" }, + "description": { "type": "string", "title": "Description" }, + "avatar_url": { "type": "string", "title": "Avatar Url" }, + "num_agents": { "type": "integer", "title": "Num Agents" }, + "agent_rating": { "type": "number", "title": "Agent Rating" }, + "agent_runs": { "type": "integer", "title": "Agent Runs" }, + "is_featured": { "type": "boolean", "title": "Is Featured" } + }, + "type": "object", + "required": [ + "name", + "username", + "description", + "avatar_url", + "num_agents", + "agent_rating", + "agent_runs", + "is_featured" + ], + "title": "Creator" + }, + "CreatorDetails": { + "properties": { + "name": { "type": "string", "title": "Name" }, + "username": { "type": "string", "title": "Username" }, + "description": { "type": "string", "title": "Description" }, + "links": { + "items": { "type": "string" }, + "type": "array", + "title": "Links" + }, + "avatar_url": { "type": "string", "title": "Avatar Url" }, + "agent_rating": { "type": "number", "title": "Agent Rating" }, + "agent_runs": { "type": "integer", "title": "Agent Runs" }, + "top_categories": { + "items": { "type": "string" }, + "type": "array", + "title": "Top Categories" + } + }, + "type": "object", + "required": [ + "name", + "username", + "description", + "links", + "avatar_url", + "agent_rating", + "agent_runs", + "top_categories" + ], + "title": "CreatorDetails" + }, + "CreatorsResponse": { + "properties": { + "creators": { + "items": { "$ref": "#/components/schemas/Creator" }, + "type": "array", + "title": "Creators" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["creators", "pagination"], + "title": "CreatorsResponse" + }, + "CredentialsDeletionNeedsConfirmationResponse": { + "properties": { + "deleted": { + "type": "boolean", + "const": false, + "title": "Deleted", + "default": false + }, + "need_confirmation": { + "type": "boolean", + "const": true, + "title": "Need Confirmation", + "default": true + }, + "message": { "type": "string", "title": "Message" } + }, + "type": "object", + "required": ["message"], + "title": "CredentialsDeletionNeedsConfirmationResponse" + }, + "CredentialsDeletionResponse": { + "properties": { + "deleted": { + "type": "boolean", + "const": true, + "title": "Deleted", + "default": true + }, + "revoked": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Revoked", + "description": "Indicates whether the credentials were also revoked by their provider. `None`/`null` if not applicable, e.g. when deleting non-revocable credentials such as API keys." + } + }, + "type": "object", + "required": ["revoked"], + "title": "CredentialsDeletionResponse" + }, + "CredentialsMetaInput": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "title": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Title" + }, + "provider": { "$ref": "#/components/schemas/ProviderName" }, + "type": { + "type": "string", + "enum": ["api_key", "oauth2", "user_password"], + "title": "Type" + } + }, + "type": "object", + "required": ["id", "provider", "type"], + "title": "CredentialsMetaInput", + "credentials_provider": [], + "credentials_types": [] + }, + "CredentialsMetaResponse": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "provider": { "type": "string", "title": "Provider" }, + "type": { + "type": "string", + "enum": ["api_key", "oauth2", "user_password"], + "title": "Type" + }, + "title": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Title" + }, + "scopes": { + "anyOf": [ + { "items": { "type": "string" }, "type": "array" }, + { "type": "null" } + ], + "title": "Scopes" + }, + "username": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Username" + } + }, + "type": "object", + "required": ["id", "provider", "type", "title", "scopes", "username"], + "title": "CredentialsMetaResponse" + }, + "CreditTransactionType": { + "type": "string", + "enum": ["TOP_UP", "USAGE", "GRANT", "REFUND", "CARD_CHECK"], + "title": "CreditTransactionType" + }, + "DeleteGraphResponse": { + "properties": { + "version_counts": { "type": "integer", "title": "Version Counts" } + }, + "type": "object", + "required": ["version_counts"], + "title": "DeleteGraphResponse" + }, + "Document": { + "properties": { + "url": { "type": "string", "title": "Url" }, + "relevance_score": { "type": "number", "title": "Relevance Score" } + }, + "type": "object", + "required": ["url", "relevance_score"], + "title": "Document" + }, + "ExecuteGraphResponse": { + "properties": { + "graph_exec_id": { "type": "string", "title": "Graph Exec Id" } + }, + "type": "object", + "required": ["graph_exec_id"], + "title": "ExecuteGraphResponse" + }, + "Graph": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "nodes": { + "items": { "$ref": "#/components/schemas/Node" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + }, + "sub_graphs": { + "items": { "$ref": "#/components/schemas/BaseGraph-Input" }, + "type": "array", + "title": "Sub Graphs", + "default": [] + } + }, + "type": "object", + "required": ["name", "description"], + "title": "Graph" + }, + "GraphExecution": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "user_id": { "type": "string", "title": "User Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "preset_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Preset Id" + }, + "status": { "$ref": "#/components/schemas/AgentExecutionStatus" }, + "started_at": { + "type": "string", + "format": "date-time", + "title": "Started At" + }, + "ended_at": { + "type": "string", + "format": "date-time", + "title": "Ended At" + }, + "stats": { + "anyOf": [ + { "$ref": "#/components/schemas/Stats" }, + { "type": "null" } + ] + }, + "inputs": { + "additionalProperties": true, + "type": "object", + "title": "Inputs" + }, + "outputs": { + "additionalProperties": { "items": {}, "type": "array" }, + "type": "object", + "title": "Outputs" + } + }, + "type": "object", + "required": [ + "user_id", + "graph_id", + "graph_version", + "status", + "started_at", + "ended_at", + "stats", + "inputs", + "outputs" + ], + "title": "GraphExecution" + }, + "GraphExecutionJobInfo": { + "properties": { + "graph_id": { "type": "string", "title": "Graph Id" }, + "input_data": { + "additionalProperties": true, + "type": "object", + "title": "Input Data" + }, + "user_id": { "type": "string", "title": "User Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "cron": { "type": "string", "title": "Cron" }, + "id": { "type": "string", "title": "Id" }, + "name": { "type": "string", "title": "Name" }, + "next_run_time": { "type": "string", "title": "Next Run Time" } + }, + "type": "object", + "required": [ + "graph_id", + "input_data", + "user_id", + "graph_version", + "cron", + "id", + "name", + "next_run_time" + ], + "title": "GraphExecutionJobInfo" + }, + "GraphExecutionMeta": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "user_id": { "type": "string", "title": "User Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "preset_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Preset Id" + }, + "status": { "$ref": "#/components/schemas/AgentExecutionStatus" }, + "started_at": { + "type": "string", + "format": "date-time", + "title": "Started At" + }, + "ended_at": { + "type": "string", + "format": "date-time", + "title": "Ended At" + }, + "stats": { + "anyOf": [ + { "$ref": "#/components/schemas/Stats" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": [ + "user_id", + "graph_id", + "graph_version", + "status", + "started_at", + "ended_at", + "stats" + ], + "title": "GraphExecutionMeta" + }, + "GraphExecutionWithNodes": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "user_id": { "type": "string", "title": "User Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "preset_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Preset Id" + }, + "status": { "$ref": "#/components/schemas/AgentExecutionStatus" }, + "started_at": { + "type": "string", + "format": "date-time", + "title": "Started At" + }, + "ended_at": { + "type": "string", + "format": "date-time", + "title": "Ended At" + }, + "stats": { + "anyOf": [ + { "$ref": "#/components/schemas/Stats" }, + { "type": "null" } + ] + }, + "inputs": { + "additionalProperties": true, + "type": "object", + "title": "Inputs" + }, + "outputs": { + "additionalProperties": { "items": {}, "type": "array" }, + "type": "object", + "title": "Outputs" + }, + "node_executions": { + "items": { "$ref": "#/components/schemas/NodeExecutionResult" }, + "type": "array", + "title": "Node Executions" + } + }, + "type": "object", + "required": [ + "user_id", + "graph_id", + "graph_version", + "status", + "started_at", + "ended_at", + "stats", + "inputs", + "outputs", + "node_executions" + ], + "title": "GraphExecutionWithNodes" + }, + "GraphModel": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "version": { "type": "integer", "title": "Version", "default": 1 }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "nodes": { + "items": { "$ref": "#/components/schemas/NodeModel" }, + "type": "array", + "title": "Nodes", + "default": [] + }, + "links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Links", + "default": [] + }, + "forked_from_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Forked From Id" + }, + "forked_from_version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Forked From Version" + }, + "sub_graphs": { + "items": { "$ref": "#/components/schemas/BaseGraph-Output" }, + "type": "array", + "title": "Sub Graphs", + "default": [] + }, + "user_id": { "type": "string", "title": "User Id" }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema", + "readOnly": true + }, + "output_schema": { + "additionalProperties": true, + "type": "object", + "title": "Output Schema", + "readOnly": true + }, + "credentials_input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Credentials Input Schema", + "readOnly": true + }, + "has_webhook_trigger": { + "type": "boolean", + "title": "Has Webhook Trigger", + "readOnly": true + } + }, + "type": "object", + "required": [ + "name", + "description", + "user_id", + "input_schema", + "output_schema", + "credentials_input_schema", + "has_webhook_trigger" + ], + "title": "GraphModel" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { "$ref": "#/components/schemas/ValidationError" }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "LibraryAgent": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "image_url": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Image Url" + }, + "creator_name": { "type": "string", "title": "Creator Name" }, + "creator_image_url": { + "type": "string", + "title": "Creator Image Url" + }, + "status": { "$ref": "#/components/schemas/LibraryAgentStatus" }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "input_schema": { + "additionalProperties": true, + "type": "object", + "title": "Input Schema" + }, + "new_output": { "type": "boolean", "title": "New Output" }, + "can_access_graph": { + "type": "boolean", + "title": "Can Access Graph" + }, + "is_latest_version": { + "type": "boolean", + "title": "Is Latest Version" + } + }, + "type": "object", + "required": [ + "id", + "graph_id", + "graph_version", + "image_url", + "creator_name", + "creator_image_url", + "status", + "updated_at", + "name", + "description", + "input_schema", + "new_output", + "can_access_graph", + "is_latest_version" + ], + "title": "LibraryAgent", + "description": "Represents an agent in the library, including metadata for display and\nuser interaction within the system." + }, + "LibraryAgentPreset": { + "properties": { + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "inputs": { + "additionalProperties": true, + "type": "object", + "title": "Inputs" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + }, + "id": { "type": "string", "title": "Id" }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "graph_id", + "graph_version", + "inputs", + "name", + "description", + "id", + "updated_at" + ], + "title": "LibraryAgentPreset", + "description": "Represents a preset configuration for a library agent." + }, + "LibraryAgentPresetCreatable": { + "properties": { + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "inputs": { + "additionalProperties": true, + "type": "object", + "title": "Inputs" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + } + }, + "type": "object", + "required": [ + "graph_id", + "graph_version", + "inputs", + "name", + "description" + ], + "title": "LibraryAgentPresetCreatable", + "description": "Request model used when creating a new preset for a library agent." + }, + "LibraryAgentPresetCreatableFromGraphExecution": { + "properties": { + "graph_execution_id": { + "type": "string", + "title": "Graph Execution Id" + }, + "name": { "type": "string", "title": "Name" }, + "description": { "type": "string", "title": "Description" }, + "is_active": { + "type": "boolean", + "title": "Is Active", + "default": true + } + }, + "type": "object", + "required": ["graph_execution_id", "name", "description"], + "title": "LibraryAgentPresetCreatableFromGraphExecution", + "description": "Request model used when creating a new preset for a library agent." + }, + "LibraryAgentPresetResponse": { + "properties": { + "presets": { + "items": { "$ref": "#/components/schemas/LibraryAgentPreset" }, + "type": "array", + "title": "Presets" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["presets", "pagination"], + "title": "LibraryAgentPresetResponse", + "description": "Response schema for a list of agent presets and pagination info." + }, + "LibraryAgentPresetUpdatable": { + "properties": { + "inputs": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Inputs" + }, + "name": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Name" + }, + "description": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Description" + }, + "is_active": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Is Active" + } + }, + "type": "object", + "title": "LibraryAgentPresetUpdatable", + "description": "Request model used when updating a preset for a library agent." + }, + "LibraryAgentResponse": { + "properties": { + "agents": { + "items": { "$ref": "#/components/schemas/LibraryAgent" }, + "type": "array", + "title": "Agents" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["agents", "pagination"], + "title": "LibraryAgentResponse", + "description": "Response schema for a list of library agents and pagination info." + }, + "LibraryAgentSort": { + "type": "string", + "enum": ["createdAt", "updatedAt"], + "title": "LibraryAgentSort", + "description": "Possible sort options for sorting library agents." + }, + "LibraryAgentStatus": { + "type": "string", + "enum": ["COMPLETED", "HEALTHY", "WAITING", "ERROR"], + "title": "LibraryAgentStatus" + }, + "LibraryAgentUpdateRequest": { + "properties": { + "auto_update_version": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Auto Update Version", + "description": "Auto-update the agent version" + }, + "is_favorite": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Is Favorite", + "description": "Mark the agent as a favorite" + }, + "is_archived": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Is Archived", + "description": "Archive the agent" + }, + "is_deleted": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Is Deleted", + "description": "Delete the agent" + } + }, + "type": "object", + "title": "LibraryAgentUpdateRequest", + "description": "Schema for updating a library agent via PUT.\n\nIncludes flags for auto-updating version, marking as favorite,\narchiving, or deleting." + }, + "Link": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "source_id": { "type": "string", "title": "Source Id" }, + "sink_id": { "type": "string", "title": "Sink Id" }, + "source_name": { "type": "string", "title": "Source Name" }, + "sink_name": { "type": "string", "title": "Sink Name" }, + "is_static": { + "type": "boolean", + "title": "Is Static", + "default": false + } + }, + "type": "object", + "required": ["source_id", "sink_id", "source_name", "sink_name"], + "title": "Link" + }, + "LoginResponse": { + "properties": { + "login_url": { "type": "string", "title": "Login Url" }, + "state_token": { "type": "string", "title": "State Token" } + }, + "type": "object", + "required": ["login_url", "state_token"], + "title": "LoginResponse" + }, + "Message": { + "properties": { + "query": { "type": "string", "title": "Query" }, + "response": { "type": "string", "title": "Response" } + }, + "type": "object", + "required": ["query", "response"], + "title": "Message" + }, + "MyAgent": { + "properties": { + "agent_id": { "type": "string", "title": "Agent Id" }, + "agent_version": { "type": "integer", "title": "Agent Version" }, + "agent_name": { "type": "string", "title": "Agent Name" }, + "agent_image": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Agent Image" + }, + "description": { "type": "string", "title": "Description" }, + "last_edited": { + "type": "string", + "format": "date-time", + "title": "Last Edited" + } + }, + "type": "object", + "required": [ + "agent_id", + "agent_version", + "agent_name", + "description", + "last_edited" + ], + "title": "MyAgent" + }, + "MyAgentsResponse": { + "properties": { + "agents": { + "items": { "$ref": "#/components/schemas/MyAgent" }, + "type": "array", + "title": "Agents" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["agents", "pagination"], + "title": "MyAgentsResponse" + }, + "Node": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "block_id": { "type": "string", "title": "Block Id" }, + "input_default": { + "additionalProperties": true, + "type": "object", + "title": "Input Default", + "default": {} + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata", + "default": {} + }, + "input_links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Input Links", + "default": [] + }, + "output_links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Output Links", + "default": [] + } + }, + "type": "object", + "required": ["block_id"], + "title": "Node" + }, + "NodeExecutionResult": { + "properties": { + "user_id": { "type": "string", "title": "User Id" }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "graph_exec_id": { "type": "string", "title": "Graph Exec Id" }, + "node_exec_id": { "type": "string", "title": "Node Exec Id" }, + "node_id": { "type": "string", "title": "Node Id" }, + "block_id": { "type": "string", "title": "Block Id" }, + "status": { "$ref": "#/components/schemas/AgentExecutionStatus" }, + "input_data": { + "additionalProperties": true, + "type": "object", + "title": "Input Data" + }, + "output_data": { + "additionalProperties": { "items": {}, "type": "array" }, + "type": "object", + "title": "Output Data" + }, + "add_time": { + "type": "string", + "format": "date-time", + "title": "Add Time" + }, + "queue_time": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Queue Time" + }, + "start_time": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Start Time" + }, + "end_time": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "End Time" + } + }, + "type": "object", + "required": [ + "user_id", + "graph_id", + "graph_version", + "graph_exec_id", + "node_exec_id", + "node_id", + "block_id", + "status", + "input_data", + "output_data", + "add_time", + "queue_time", + "start_time", + "end_time" + ], + "title": "NodeExecutionResult" + }, + "NodeModel": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "block_id": { "type": "string", "title": "Block Id" }, + "input_default": { + "additionalProperties": true, + "type": "object", + "title": "Input Default", + "default": {} + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata", + "default": {} + }, + "input_links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Input Links", + "default": [] + }, + "output_links": { + "items": { "$ref": "#/components/schemas/Link" }, + "type": "array", + "title": "Output Links", + "default": [] + }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" }, + "webhook_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Webhook Id" + }, + "webhook": { + "anyOf": [ + { "$ref": "#/components/schemas/Webhook" }, + { "type": "null" } + ] + } + }, + "type": "object", + "required": ["block_id", "graph_id", "graph_version"], + "title": "NodeModel" + }, + "NotificationPreference": { + "properties": { + "user_id": { "type": "string", "title": "User Id" }, + "email": { "type": "string", "format": "email", "title": "Email" }, + "preferences": { + "additionalProperties": { "type": "boolean" }, + "propertyNames": { + "$ref": "#/components/schemas/NotificationType" + }, + "type": "object", + "title": "Preferences", + "description": "Which notifications the user wants" + }, + "daily_limit": { + "type": "integer", + "title": "Daily Limit", + "default": 10 + }, + "emails_sent_today": { + "type": "integer", + "title": "Emails Sent Today", + "default": 0 + }, + "last_reset_date": { + "type": "string", + "format": "date-time", + "title": "Last Reset Date" + } + }, + "type": "object", + "required": ["user_id", "email"], + "title": "NotificationPreference" + }, + "NotificationPreferenceDTO": { + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "Email", + "description": "User's email address" + }, + "preferences": { + "additionalProperties": { "type": "boolean" }, + "propertyNames": { + "$ref": "#/components/schemas/NotificationType" + }, + "type": "object", + "title": "Preferences", + "description": "Which notifications the user wants" + }, + "daily_limit": { + "type": "integer", + "title": "Daily Limit", + "description": "Max emails per day" + } + }, + "type": "object", + "required": ["email", "preferences", "daily_limit"], + "title": "NotificationPreferenceDTO" + }, + "NotificationType": { + "type": "string", + "enum": [ + "AGENT_RUN", + "ZERO_BALANCE", + "LOW_BALANCE", + "BLOCK_EXECUTION_FAILED", + "CONTINUOUS_AGENT_ERROR", + "DAILY_SUMMARY", + "WEEKLY_SUMMARY", + "MONTHLY_SUMMARY", + "REFUND_REQUEST", + "REFUND_PROCESSED" + ], + "title": "NotificationType" + }, + "OAuth2Credentials": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "provider": { "type": "string", "title": "Provider" }, + "title": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Title" + }, + "type": { + "type": "string", + "const": "oauth2", + "title": "Type", + "default": "oauth2" + }, + "username": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Username" + }, + "access_token": { + "type": "string", + "format": "password", + "title": "Access Token", + "writeOnly": true + }, + "access_token_expires_at": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Access Token Expires At" + }, + "refresh_token": { + "anyOf": [ + { "type": "string", "format": "password", "writeOnly": true }, + { "type": "null" } + ], + "title": "Refresh Token" + }, + "refresh_token_expires_at": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Refresh Token Expires At" + }, + "scopes": { + "items": { "type": "string" }, + "type": "array", + "title": "Scopes" + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": ["provider", "access_token", "scopes"], + "title": "OAuth2Credentials" + }, + "OnboardingStep": { + "type": "string", + "enum": [ + "WELCOME", + "USAGE_REASON", + "INTEGRATIONS", + "AGENT_CHOICE", + "AGENT_NEW_RUN", + "AGENT_INPUT", + "CONGRATS", + "GET_RESULTS", + "MARKETPLACE_VISIT", + "MARKETPLACE_ADD_AGENT", + "MARKETPLACE_RUN_AGENT", + "BUILDER_OPEN", + "BUILDER_SAVE_AGENT", + "BUILDER_RUN_AGENT" + ], + "title": "OnboardingStep" + }, + "Pagination": { + "properties": { + "total_items": { + "type": "integer", + "title": "Total Items", + "description": "Total number of items.", + "examples": [42] + }, + "total_pages": { + "type": "integer", + "title": "Total Pages", + "description": "Total number of pages.", + "examples": [2] + }, + "current_page": { + "type": "integer", + "title": "Current Page", + "description": "Current_page page number.", + "examples": [1] + }, + "page_size": { + "type": "integer", + "title": "Page Size", + "description": "Number of items per page.", + "examples": [25] + } + }, + "type": "object", + "required": ["total_items", "total_pages", "current_page", "page_size"], + "title": "Pagination" + }, + "PostmarkBounceEnum": { + "type": "integer", + "enum": [ + 1, 2, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, + 100000, 100001, 100002, 100003, 100006, 100007, 100008, 100009, 100010 + ], + "title": "PostmarkBounceEnum" + }, + "PostmarkBounceWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "Bounce", + "title": "Recordtype", + "default": "Bounce" + }, + "ID": { "type": "integer", "title": "Id" }, + "Type": { "type": "string", "title": "Type" }, + "TypeCode": { "$ref": "#/components/schemas/PostmarkBounceEnum" }, + "Tag": { "type": "string", "title": "Tag" }, + "MessageID": { "type": "string", "title": "Messageid" }, + "Details": { "type": "string", "title": "Details" }, + "Email": { "type": "string", "title": "Email" }, + "From": { "type": "string", "title": "From" }, + "BouncedAt": { "type": "string", "title": "Bouncedat" }, + "Inactive": { "type": "boolean", "title": "Inactive" }, + "DumpAvailable": { "type": "boolean", "title": "Dumpavailable" }, + "CanActivate": { "type": "boolean", "title": "Canactivate" }, + "Subject": { "type": "string", "title": "Subject" }, + "ServerID": { "type": "integer", "title": "Serverid" }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "Content": { "type": "string", "title": "Content" }, + "Name": { "type": "string", "title": "Name" }, + "Description": { "type": "string", "title": "Description" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "ID", + "Type", + "TypeCode", + "Tag", + "MessageID", + "Details", + "Email", + "From", + "BouncedAt", + "Inactive", + "DumpAvailable", + "CanActivate", + "Subject", + "ServerID", + "MessageStream", + "Content", + "Name", + "Description", + "Metadata" + ], + "title": "PostmarkBounceWebhook" + }, + "PostmarkClickWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "Click", + "title": "Recordtype", + "default": "Click" + }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + }, + "Recipient": { "type": "string", "title": "Recipient" }, + "MessageID": { "type": "string", "title": "Messageid" }, + "ReceivedAt": { "type": "string", "title": "Receivedat" }, + "Platform": { "type": "string", "title": "Platform" }, + "ClickLocation": { "type": "string", "title": "Clicklocation" }, + "OriginalLink": { "type": "string", "title": "Originallink" }, + "Tag": { "type": "string", "title": "Tag" }, + "UserAgent": { "type": "string", "title": "Useragent" }, + "OS": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Os" + }, + "Client": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Client" + }, + "Geo": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Geo" + } + }, + "type": "object", + "required": [ + "MessageStream", + "Metadata", + "Recipient", + "MessageID", + "ReceivedAt", + "Platform", + "ClickLocation", + "OriginalLink", + "Tag", + "UserAgent", + "OS", + "Client", + "Geo" + ], + "title": "PostmarkClickWebhook" + }, + "PostmarkDeliveryWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "Delivery", + "title": "Recordtype", + "default": "Delivery" + }, + "ServerID": { "type": "integer", "title": "Serverid" }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "MessageID": { "type": "string", "title": "Messageid" }, + "Recipient": { "type": "string", "title": "Recipient" }, + "Tag": { "type": "string", "title": "Tag" }, + "DeliveredAt": { "type": "string", "title": "Deliveredat" }, + "Details": { "type": "string", "title": "Details" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "ServerID", + "MessageStream", + "MessageID", + "Recipient", + "Tag", + "DeliveredAt", + "Details", + "Metadata" + ], + "title": "PostmarkDeliveryWebhook" + }, + "PostmarkOpenWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "Open", + "title": "Recordtype", + "default": "Open" + }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + }, + "FirstOpen": { "type": "boolean", "title": "Firstopen" }, + "Recipient": { "type": "string", "title": "Recipient" }, + "MessageID": { "type": "string", "title": "Messageid" }, + "ReceivedAt": { "type": "string", "title": "Receivedat" }, + "Platform": { "type": "string", "title": "Platform" }, + "ReadSeconds": { "type": "integer", "title": "Readseconds" }, + "Tag": { "type": "string", "title": "Tag" }, + "UserAgent": { "type": "string", "title": "Useragent" }, + "OS": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Os" + }, + "Client": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Client" + }, + "Geo": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Geo" + } + }, + "type": "object", + "required": [ + "MessageStream", + "Metadata", + "FirstOpen", + "Recipient", + "MessageID", + "ReceivedAt", + "Platform", + "ReadSeconds", + "Tag", + "UserAgent", + "OS", + "Client", + "Geo" + ], + "title": "PostmarkOpenWebhook" + }, + "PostmarkSpamComplaintWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "SpamComplaint", + "title": "Recordtype", + "default": "SpamComplaint" + }, + "ID": { "type": "integer", "title": "Id" }, + "Type": { "type": "string", "title": "Type" }, + "TypeCode": { "type": "integer", "title": "Typecode" }, + "Tag": { "type": "string", "title": "Tag" }, + "MessageID": { "type": "string", "title": "Messageid" }, + "Details": { "type": "string", "title": "Details" }, + "Email": { "type": "string", "title": "Email" }, + "From": { "type": "string", "title": "From" }, + "BouncedAt": { "type": "string", "title": "Bouncedat" }, + "Inactive": { "type": "boolean", "title": "Inactive" }, + "DumpAvailable": { "type": "boolean", "title": "Dumpavailable" }, + "CanActivate": { "type": "boolean", "title": "Canactivate" }, + "Subject": { "type": "string", "title": "Subject" }, + "ServerID": { "type": "integer", "title": "Serverid" }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "Content": { "type": "string", "title": "Content" }, + "Name": { "type": "string", "title": "Name" }, + "Description": { "type": "string", "title": "Description" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "ID", + "Type", + "TypeCode", + "Tag", + "MessageID", + "Details", + "Email", + "From", + "BouncedAt", + "Inactive", + "DumpAvailable", + "CanActivate", + "Subject", + "ServerID", + "MessageStream", + "Content", + "Name", + "Description", + "Metadata" + ], + "title": "PostmarkSpamComplaintWebhook" + }, + "PostmarkSubscriptionChangeWebhook": { + "properties": { + "RecordType": { + "type": "string", + "const": "SubscriptionChange", + "title": "Recordtype", + "default": "SubscriptionChange" + }, + "MessageID": { "type": "string", "title": "Messageid" }, + "ServerID": { "type": "integer", "title": "Serverid" }, + "MessageStream": { "type": "string", "title": "Messagestream" }, + "ChangedAt": { "type": "string", "title": "Changedat" }, + "Recipient": { "type": "string", "title": "Recipient" }, + "Origin": { "type": "string", "title": "Origin" }, + "SuppressSending": { "type": "boolean", "title": "Suppresssending" }, + "SuppressionReason": { + "type": "string", + "title": "Suppressionreason" + }, + "Tag": { "type": "string", "title": "Tag" }, + "Metadata": { + "additionalProperties": { "type": "string" }, + "type": "object", + "title": "Metadata" + } + }, + "type": "object", + "required": [ + "MessageID", + "ServerID", + "MessageStream", + "ChangedAt", + "Recipient", + "Origin", + "SuppressSending", + "SuppressionReason", + "Tag", + "Metadata" + ], + "title": "PostmarkSubscriptionChangeWebhook" + }, + "Profile": { + "properties": { + "name": { "type": "string", "title": "Name" }, + "username": { "type": "string", "title": "Username" }, + "description": { "type": "string", "title": "Description" }, + "links": { + "items": { "type": "string" }, + "type": "array", + "title": "Links" + }, + "avatar_url": { "type": "string", "title": "Avatar Url" }, + "is_featured": { + "type": "boolean", + "title": "Is Featured", + "default": false + } + }, + "type": "object", + "required": ["name", "username", "description", "links", "avatar_url"], + "title": "Profile" + }, + "ProfileDetails": { + "properties": { + "name": { "type": "string", "title": "Name" }, + "username": { "type": "string", "title": "Username" }, + "description": { "type": "string", "title": "Description" }, + "links": { + "items": { "type": "string" }, + "type": "array", + "title": "Links" + }, + "avatar_url": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Avatar Url" + } + }, + "type": "object", + "required": ["name", "username", "description", "links"], + "title": "ProfileDetails" + }, + "ProviderName": { + "type": "string", + "enum": [ + "aiml_api", + "anthropic", + "apollo", + "compass", + "discord", + "d_id", + "e2b", + "exa", + "fal", + "generic_webhook", + "github", + "google", + "google_maps", + "groq", + "hubspot", + "ideogram", + "jina", + "linear", + "llama_api", + "medium", + "mem0", + "notion", + "nvidia", + "ollama", + "openai", + "openweathermap", + "open_router", + "pinecone", + "reddit", + "replicate", + "revid", + "screenshotone", + "slant3d", + "smartlead", + "smtp", + "twitter", + "todoist", + "unreal_speech", + "zerobounce" + ], + "title": "ProviderName" + }, + "RefundRequest": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "user_id": { "type": "string", "title": "User Id" }, + "transaction_key": { "type": "string", "title": "Transaction Key" }, + "amount": { "type": "integer", "title": "Amount" }, + "reason": { "type": "string", "title": "Reason" }, + "result": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Result" + }, + "status": { "type": "string", "title": "Status" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "user_id", + "transaction_key", + "amount", + "reason", + "status", + "created_at", + "updated_at" + ], + "title": "RefundRequest" + }, + "RequestTopUp": { + "properties": { + "credit_amount": { "type": "integer", "title": "Credit Amount" } + }, + "type": "object", + "required": ["credit_amount"], + "title": "RequestTopUp" + }, + "ReviewSubmissionRequest": { + "properties": { + "store_listing_version_id": { + "type": "string", + "title": "Store Listing Version Id" + }, + "is_approved": { "type": "boolean", "title": "Is Approved" }, + "comments": { "type": "string", "title": "Comments" }, + "internal_comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Internal Comments" + } + }, + "type": "object", + "required": ["store_listing_version_id", "is_approved", "comments"], + "title": "ReviewSubmissionRequest" + }, + "ScheduleCreationRequest": { + "properties": { + "cron": { "type": "string", "title": "Cron" }, + "input_data": { + "additionalProperties": true, + "type": "object", + "title": "Input Data" + }, + "graph_id": { "type": "string", "title": "Graph Id" }, + "graph_version": { "type": "integer", "title": "Graph Version" } + }, + "type": "object", + "required": ["cron", "input_data", "graph_id", "graph_version"], + "title": "ScheduleCreationRequest" + }, + "SetGraphActiveVersion": { + "properties": { + "active_graph_version": { + "type": "integer", + "title": "Active Graph Version" + } + }, + "type": "object", + "required": ["active_graph_version"], + "title": "SetGraphActiveVersion" + }, + "Stats": { + "properties": { + "cost": { + "type": "integer", + "title": "Cost", + "description": "Execution cost (cents)", + "default": 0 + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "Seconds from start to end of run", + "default": 0 + }, + "duration_cpu_only": { + "type": "number", + "title": "Duration Cpu Only", + "description": "CPU sec of duration", + "default": 0 + }, + "node_exec_time": { + "type": "number", + "title": "Node Exec Time", + "description": "Seconds of total node runtime", + "default": 0 + }, + "node_exec_time_cpu_only": { + "type": "number", + "title": "Node Exec Time Cpu Only", + "description": "CPU sec of node_exec_time", + "default": 0 + }, + "node_exec_count": { + "type": "integer", + "title": "Node Exec Count", + "description": "Number of node executions", + "default": 0 + }, + "node_error_count": { + "type": "integer", + "title": "Node Error Count", + "description": "Number of node errors", + "default": 0 + }, + "error": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Error", + "description": "Error message if any" + } + }, + "additionalProperties": true, + "type": "object", + "title": "Stats" + }, + "StoreAgent": { + "properties": { + "slug": { "type": "string", "title": "Slug" }, + "agent_name": { "type": "string", "title": "Agent Name" }, + "agent_image": { "type": "string", "title": "Agent Image" }, + "creator": { "type": "string", "title": "Creator" }, + "creator_avatar": { "type": "string", "title": "Creator Avatar" }, + "sub_heading": { "type": "string", "title": "Sub Heading" }, + "description": { "type": "string", "title": "Description" }, + "runs": { "type": "integer", "title": "Runs" }, + "rating": { "type": "number", "title": "Rating" } + }, + "type": "object", + "required": [ + "slug", + "agent_name", + "agent_image", + "creator", + "creator_avatar", + "sub_heading", + "description", + "runs", + "rating" + ], + "title": "StoreAgent" + }, + "StoreAgentDetails": { + "properties": { + "store_listing_version_id": { + "type": "string", + "title": "Store Listing Version Id" + }, + "slug": { "type": "string", "title": "Slug" }, + "agent_name": { "type": "string", "title": "Agent Name" }, + "agent_video": { "type": "string", "title": "Agent Video" }, + "agent_image": { + "items": { "type": "string" }, + "type": "array", + "title": "Agent Image" + }, + "creator": { "type": "string", "title": "Creator" }, + "creator_avatar": { "type": "string", "title": "Creator Avatar" }, + "sub_heading": { "type": "string", "title": "Sub Heading" }, + "description": { "type": "string", "title": "Description" }, + "categories": { + "items": { "type": "string" }, + "type": "array", + "title": "Categories" + }, + "runs": { "type": "integer", "title": "Runs" }, + "rating": { "type": "number", "title": "Rating" }, + "versions": { + "items": { "type": "string" }, + "type": "array", + "title": "Versions" + }, + "last_updated": { + "type": "string", + "format": "date-time", + "title": "Last Updated" + }, + "active_version_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Active Version Id" + }, + "has_approved_version": { + "type": "boolean", + "title": "Has Approved Version", + "default": false + } + }, + "type": "object", + "required": [ + "store_listing_version_id", + "slug", + "agent_name", + "agent_video", + "agent_image", + "creator", + "creator_avatar", + "sub_heading", + "description", + "categories", + "runs", + "rating", + "versions", + "last_updated" + ], + "title": "StoreAgentDetails" + }, + "StoreAgentsResponse": { + "properties": { + "agents": { + "items": { "$ref": "#/components/schemas/StoreAgent" }, + "type": "array", + "title": "Agents" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["agents", "pagination"], + "title": "StoreAgentsResponse" + }, + "StoreListingWithVersions": { + "properties": { + "listing_id": { "type": "string", "title": "Listing Id" }, + "slug": { "type": "string", "title": "Slug" }, + "agent_id": { "type": "string", "title": "Agent Id" }, + "agent_version": { "type": "integer", "title": "Agent Version" }, + "active_version_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Active Version Id" + }, + "has_approved_version": { + "type": "boolean", + "title": "Has Approved Version", + "default": false + }, + "creator_email": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Creator Email" + }, + "latest_version": { + "anyOf": [ + { "$ref": "#/components/schemas/StoreSubmission" }, + { "type": "null" } + ] + }, + "versions": { + "items": { "$ref": "#/components/schemas/StoreSubmission" }, + "type": "array", + "title": "Versions", + "default": [] + } + }, + "type": "object", + "required": ["listing_id", "slug", "agent_id", "agent_version"], + "title": "StoreListingWithVersions", + "description": "A store listing with its version history" + }, + "StoreListingsWithVersionsResponse": { + "properties": { + "listings": { + "items": { + "$ref": "#/components/schemas/StoreListingWithVersions" + }, + "type": "array", + "title": "Listings" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["listings", "pagination"], + "title": "StoreListingsWithVersionsResponse", + "description": "Response model for listings with version history" + }, + "StoreReview": { + "properties": { + "score": { "type": "integer", "title": "Score" }, + "comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Comments" + } + }, + "type": "object", + "required": ["score"], + "title": "StoreReview" + }, + "StoreReviewCreate": { + "properties": { + "store_listing_version_id": { + "type": "string", + "title": "Store Listing Version Id" + }, + "score": { "type": "integer", "title": "Score" }, + "comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Comments" + } + }, + "type": "object", + "required": ["store_listing_version_id", "score"], + "title": "StoreReviewCreate" + }, + "StoreSubmission": { + "properties": { + "agent_id": { "type": "string", "title": "Agent Id" }, + "agent_version": { "type": "integer", "title": "Agent Version" }, + "name": { "type": "string", "title": "Name" }, + "sub_heading": { "type": "string", "title": "Sub Heading" }, + "slug": { "type": "string", "title": "Slug" }, + "description": { "type": "string", "title": "Description" }, + "image_urls": { + "items": { "type": "string" }, + "type": "array", + "title": "Image Urls" + }, + "date_submitted": { + "type": "string", + "format": "date-time", + "title": "Date Submitted" + }, + "status": { "$ref": "#/components/schemas/SubmissionStatus" }, + "runs": { "type": "integer", "title": "Runs" }, + "rating": { "type": "number", "title": "Rating" }, + "store_listing_version_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Store Listing Version Id" + }, + "version": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Version" + }, + "reviewer_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Reviewer Id" + }, + "review_comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Review Comments" + }, + "internal_comments": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Internal Comments" + }, + "reviewed_at": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Reviewed At" + }, + "changes_summary": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Changes Summary" + } + }, + "type": "object", + "required": [ + "agent_id", + "agent_version", + "name", + "sub_heading", + "slug", + "description", + "image_urls", + "date_submitted", + "status", + "runs", + "rating" + ], + "title": "StoreSubmission" + }, + "StoreSubmissionRequest": { + "properties": { + "agent_id": { "type": "string", "title": "Agent Id" }, + "agent_version": { "type": "integer", "title": "Agent Version" }, + "slug": { "type": "string", "title": "Slug" }, + "name": { "type": "string", "title": "Name" }, + "sub_heading": { "type": "string", "title": "Sub Heading" }, + "video_url": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Video Url" + }, + "image_urls": { + "items": { "type": "string" }, + "type": "array", + "title": "Image Urls", + "default": [] + }, + "description": { + "type": "string", + "title": "Description", + "default": "" + }, + "categories": { + "items": { "type": "string" }, + "type": "array", + "title": "Categories", + "default": [] + }, + "changes_summary": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Changes Summary" + } + }, + "type": "object", + "required": [ + "agent_id", + "agent_version", + "slug", + "name", + "sub_heading" + ], + "title": "StoreSubmissionRequest" + }, + "StoreSubmissionsResponse": { + "properties": { + "submissions": { + "items": { "$ref": "#/components/schemas/StoreSubmission" }, + "type": "array", + "title": "Submissions" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["submissions", "pagination"], + "title": "StoreSubmissionsResponse" + }, + "SubmissionStatus": { + "type": "string", + "enum": ["DRAFT", "PENDING", "APPROVED", "REJECTED"], + "title": "SubmissionStatus" + }, + "TransactionHistory": { + "properties": { + "transactions": { + "items": { "$ref": "#/components/schemas/UserTransaction" }, + "type": "array", + "title": "Transactions" + }, + "next_transaction_time": { + "anyOf": [ + { "type": "string", "format": "date-time" }, + { "type": "null" } + ], + "title": "Next Transaction Time" + } + }, + "type": "object", + "required": ["transactions", "next_transaction_time"], + "title": "TransactionHistory" + }, + "TurnstileVerifyRequest": { + "properties": { + "token": { + "type": "string", + "title": "Token", + "description": "The Turnstile token to verify" + }, + "action": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Action", + "description": "The action that the user is attempting to perform" + } + }, + "type": "object", + "required": ["token"], + "title": "TurnstileVerifyRequest", + "description": "Request model for verifying a Turnstile token." + }, + "TurnstileVerifyResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "description": "Whether the token verification was successful" + }, + "error": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Error", + "description": "Error message if verification failed" + }, + "challenge_timestamp": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Challenge Timestamp", + "description": "Timestamp of the challenge (ISO format)" + }, + "hostname": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Hostname", + "description": "Hostname of the site where the challenge was solved" + }, + "action": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Action", + "description": "The action associated with this verification" + } + }, + "type": "object", + "required": ["success"], + "title": "TurnstileVerifyResponse", + "description": "Response model for the Turnstile verification endpoint." + }, + "UpdatePermissionsRequest": { + "properties": { + "permissions": { + "items": { "$ref": "#/components/schemas/APIKeyPermission" }, + "type": "array", + "title": "Permissions" + } + }, + "type": "object", + "required": ["permissions"], + "title": "UpdatePermissionsRequest" + }, + "UserHistoryResponse": { + "properties": { + "history": { + "items": { "$ref": "#/components/schemas/UserTransaction" }, + "type": "array", + "title": "History" + }, + "pagination": { "$ref": "#/components/schemas/Pagination" } + }, + "type": "object", + "required": ["history", "pagination"], + "title": "UserHistoryResponse", + "description": "Response model for listings with version history" + }, + "UserOnboardingUpdate": { + "properties": { + "completedSteps": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/OnboardingStep" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Completedsteps" + }, + "notificationDot": { + "anyOf": [{ "type": "boolean" }, { "type": "null" }], + "title": "Notificationdot" + }, + "notified": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/OnboardingStep" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Notified" + }, + "usageReason": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Usagereason" + }, + "integrations": { + "anyOf": [ + { "items": { "type": "string" }, "type": "array" }, + { "type": "null" } + ], + "title": "Integrations" + }, + "otherIntegrations": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Otherintegrations" + }, + "selectedStoreListingVersionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Selectedstorelistingversionid" + }, + "agentInput": { + "anyOf": [ + { "additionalProperties": true, "type": "object" }, + { "type": "null" } + ], + "title": "Agentinput" + }, + "onboardingAgentExecutionId": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Onboardingagentexecutionid" + }, + "agentRuns": { + "anyOf": [{ "type": "integer" }, { "type": "null" }], + "title": "Agentruns" + } + }, + "type": "object", + "title": "UserOnboardingUpdate" + }, + "UserPasswordCredentials": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "provider": { "type": "string", "title": "Provider" }, + "title": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Title" + }, + "type": { + "type": "string", + "const": "user_password", + "title": "Type", + "default": "user_password" + }, + "username": { + "type": "string", + "format": "password", + "title": "Username", + "writeOnly": true + }, + "password": { + "type": "string", + "format": "password", + "title": "Password", + "writeOnly": true + } + }, + "type": "object", + "required": ["provider", "username", "password"], + "title": "UserPasswordCredentials" + }, + "UserTransaction": { + "properties": { + "transaction_key": { + "type": "string", + "title": "Transaction Key", + "default": "" + }, + "transaction_time": { + "type": "string", + "format": "date-time", + "title": "Transaction Time", + "default": "0001-01-01T00:00:00Z" + }, + "transaction_type": { + "$ref": "#/components/schemas/CreditTransactionType", + "default": "USAGE" + }, + "amount": { "type": "integer", "title": "Amount", "default": 0 }, + "running_balance": { + "type": "integer", + "title": "Running Balance", + "default": 0 + }, + "current_balance": { + "type": "integer", + "title": "Current Balance", + "default": 0 + }, + "description": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Description" + }, + "usage_graph_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Usage Graph Id" + }, + "usage_execution_id": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Usage Execution Id" + }, + "usage_node_count": { + "type": "integer", + "title": "Usage Node Count", + "default": 0 + }, + "usage_start_time": { + "type": "string", + "format": "date-time", + "title": "Usage Start Time", + "default": "9999-12-31T23:59:59.999999Z" + }, + "user_id": { "type": "string", "title": "User Id" }, + "user_email": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "User Email" + }, + "reason": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Reason" + }, + "admin_email": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Admin Email" + }, + "extra_data": { + "anyOf": [{ "type": "string" }, { "type": "null" }], + "title": "Extra Data" + } + }, + "type": "object", + "required": ["user_id"], + "title": "UserTransaction" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { "anyOf": [{ "type": "string" }, { "type": "integer" }] }, + "type": "array", + "title": "Location" + }, + "msg": { "type": "string", "title": "Message" }, + "type": { "type": "string", "title": "Error Type" } + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError" + }, + "Webhook": { + "properties": { + "id": { "type": "string", "title": "Id" }, + "user_id": { "type": "string", "title": "User Id" }, + "provider": { "$ref": "#/components/schemas/ProviderName" }, + "credentials_id": { "type": "string", "title": "Credentials Id" }, + "webhook_type": { "type": "string", "title": "Webhook Type" }, + "resource": { "type": "string", "title": "Resource" }, + "events": { + "items": { "type": "string" }, + "type": "array", + "title": "Events" + }, + "config": { + "additionalProperties": true, + "type": "object", + "title": "Config" + }, + "secret": { "type": "string", "title": "Secret" }, + "provider_webhook_id": { + "type": "string", + "title": "Provider Webhook Id" + }, + "attached_nodes": { + "anyOf": [ + { + "items": { "$ref": "#/components/schemas/NodeModel" }, + "type": "array" + }, + { "type": "null" } + ], + "title": "Attached Nodes" + }, + "url": { "type": "string", "title": "Url", "readOnly": true } + }, + "type": "object", + "required": [ + "user_id", + "provider", + "credentials_id", + "webhook_type", + "resource", + "events", + "secret", + "provider_webhook_id", + "url" + ], + "title": "Webhook" + } + }, + "securitySchemes": { + "APIKeyHeader": { + "type": "apiKey", + "in": "header", + "name": "X-Postmark-Webhook-Token" + } + } + } +} diff --git a/autogpt_platform/frontend/src/api/transformers/fix-tags.mjs b/autogpt_platform/frontend/src/api/transformers/fix-tags.mjs new file mode 100644 index 0000000000..e1bc2c4267 --- /dev/null +++ b/autogpt_platform/frontend/src/api/transformers/fix-tags.mjs @@ -0,0 +1,57 @@ +/** + * Transformer function for orval that fixes tags in OpenAPI spec. + * 1. Create a set of tags so we have unique values + * 2. Then remove public, private, v1, and v2 tags from tags array + * 3. Then arrange remaining tags alphabetically and only keep the first one + * + * @param {OpenAPIObject} inputSchema + * @return {OpenAPIObject} + */ + +export const tagTransformer = (inputSchema) => { + const processedPaths = Object.entries(inputSchema.paths || {}).reduce( + (acc, [path, pathItem]) => ({ + ...acc, + [path]: Object.entries(pathItem || {}).reduce( + (pathItemAcc, [verb, operation]) => { + if (typeof operation === "object" && operation !== null) { + // 1. Create a set of tags so we have unique values + const uniqueTags = Array.from(new Set(operation.tags || [])); + + // 2. Remove public, private, v1, and v2 tags from tags array + const filteredTags = uniqueTags.filter( + (tag) => + !["public", "private"].includes(tag.toLowerCase()) && + !/^v[12]$/i.test(tag), + ); + + // 3. Arrange tags alphabetically and only keep the first one + const sortedTags = filteredTags.sort((a, b) => a.localeCompare(b)); + const firstTag = sortedTags.length > 0 ? [sortedTags[0]] : []; + + return { + ...pathItemAcc, + [verb]: { + ...operation, + tags: firstTag, + }, + }; + } + return { + ...pathItemAcc, + [verb]: operation, + }; + }, + {}, + ), + }), + {}, + ); + + return { + ...inputSchema, + paths: processedPaths, + }; +}; + +export default tagTransformer; diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/3-services/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/3-services/page.tsx index 92623301d8..a7867aca50 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/3-services/page.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/3-services/page.tsx @@ -9,7 +9,6 @@ import { OnboardingText } from "@/components/onboarding/OnboardingText"; import { OnboardingGrid } from "@/components/onboarding/OnboardingGrid"; import { useCallback } from "react"; import OnboardingInput from "@/components/onboarding/OnboardingInput"; -import { isEmptyOrWhitespace } from "@/lib/utils"; import { useOnboarding } from "@/components/onboarding/onboarding-provider"; const services = [ diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/page.tsx index 5ea5e3adf2..7769e0c48a 100644 --- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/page.tsx +++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/5-run/page.tsx @@ -49,6 +49,7 @@ export default function Page() { .getAgentMetaByStoreListingVersionId(state?.selectedStoreListingVersionId) .then((agent) => { setAgent(agent); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const update: { [key: string]: any } = {}; // Set default values from schema Object.entries(agent.input_schema.properties).forEach( diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx index cb72daee73..bfb2b2695d 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx @@ -1,6 +1,5 @@ -import { ShoppingBag } from "lucide-react"; import { Sidebar } from "@/components/agptui/Sidebar"; -import { Users, DollarSign, LogOut } from "lucide-react"; +import { Users, DollarSign } from "lucide-react"; import { IconSliders } from "@/components/ui/icons"; diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/marketplace/actions.ts b/autogpt_platform/frontend/src/app/(platform)/admin/marketplace/actions.ts index ad7f48f24c..480e79e669 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/marketplace/actions.ts +++ b/autogpt_platform/frontend/src/app/(platform)/admin/marketplace/actions.ts @@ -3,9 +3,7 @@ import { revalidatePath } from "next/cache"; import BackendApi from "@/lib/autogpt-server-api"; import { - NotificationPreferenceDTO, StoreListingsWithVersionsResponse, - StoreSubmissionsResponse, SubmissionStatus, } from "@/lib/autogpt-server-api/types"; diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/spending/actions.ts b/autogpt_platform/frontend/src/app/(platform)/admin/spending/actions.ts index b6ef75a599..ea4a194acd 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/spending/actions.ts +++ b/autogpt_platform/frontend/src/app/(platform)/admin/spending/actions.ts @@ -29,6 +29,7 @@ export async function getUsersTransactionHistory( search?: string, transactionType?: CreditTransactionType, ): Promise<UsersBalanceHistoryResponse> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const data: Record<string, any> = { page, page_size: pageSize, diff --git a/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts b/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts index 54db111dfd..62e324761e 100644 --- a/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts +++ b/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts @@ -11,13 +11,25 @@ async function shouldShowOnboarding() { ); } +// Validate redirect URL to prevent open redirect attacks +function validateRedirectUrl(url: string): string { + // Only allow relative URLs that start with / + if (url.startsWith("/") && !url.startsWith("//")) { + return url; + } + // Default to home page for any invalid URLs + return "/"; +} + // Handle the callback to complete the user session login export async function GET(request: Request) { const { searchParams, origin } = new URL(request.url); const code = searchParams.get("code"); // if "next" is in param, use it as the redirect URL - let next = searchParams.get("next") ?? "/"; + const nextParam = searchParams.get("next") ?? "/"; + // Validate redirect URL to prevent open redirect attacks + let next = validateRedirectUrl(nextParam); if (code) { const supabase = await getServerSupabase(); @@ -26,7 +38,7 @@ export async function GET(request: Request) { return NextResponse.redirect(`${origin}/error`); } - const { data, error } = await supabase.auth.exchangeCodeForSession(code); + const { error } = await supabase.auth.exchangeCodeForSession(code); // data.session?.refresh_token is available if you need to store it for later use if (!error) { try { diff --git a/autogpt_platform/frontend/src/app/(platform)/auth/integrations/oauth_callback/route.ts b/autogpt_platform/frontend/src/app/(platform)/auth/integrations/oauth_callback/route.ts index 5d4100d48e..18e74369df 100644 --- a/autogpt_platform/frontend/src/app/(platform)/auth/integrations/oauth_callback/route.ts +++ b/autogpt_platform/frontend/src/app/(platform)/auth/integrations/oauth_callback/route.ts @@ -5,7 +5,7 @@ import { NextResponse } from "next/server"; // controlled by the CredentialsInput component. The CredentialsInput opens the login // page in a pop-up window, which then redirects to this route to close the loop. export async function GET(request: Request) { - const { searchParams, origin } = new URL(request.url); + const { searchParams } = new URL(request.url); const code = searchParams.get("code"); const state = searchParams.get("state"); diff --git a/autogpt_platform/frontend/src/app/(platform)/build/actions.ts b/autogpt_platform/frontend/src/app/(platform)/build/actions.ts index 16c577d16c..7907d919f4 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/actions.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/actions.ts @@ -1,6 +1,5 @@ "use server"; -import { revalidatePath } from "next/cache"; import BackendAPI from "@/lib/autogpt-server-api/client"; import { OttoQuery, OttoResponse } from "@/lib/autogpt-server-api/types"; diff --git a/autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx b/autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx index b462cdb704..484022f151 100644 --- a/autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx @@ -92,7 +92,7 @@ export default async function Page({ </main> </div> ); - } catch (error) { + } catch { return ( <div className="flex h-screen w-full items-center justify-center"> <div className="text-2xl text-neutral-900">Creator not found</div> diff --git a/autogpt_platform/frontend/src/app/(platform)/marketplace/search/page.tsx b/autogpt_platform/frontend/src/app/(platform)/marketplace/search/page.tsx index 88f620f6ab..d9209b2c4a 100644 --- a/autogpt_platform/frontend/src/app/(platform)/marketplace/search/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/marketplace/search/page.tsx @@ -8,6 +8,7 @@ import { Separator } from "@/components/ui/separator"; import { SearchFilterChips } from "@/components/agptui/SearchFilterChips"; import { SortDropdown } from "@/components/agptui/SortDropdown"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; +import { Creator, StoreAgent } from "@/lib/autogpt-server-api"; type MarketplaceSearchPageSearchParams = { searchTerm?: string; sort?: string }; @@ -33,8 +34,8 @@ function SearchResults({ }): React.ReactElement { const [showAgents, setShowAgents] = useState(true); const [showCreators, setShowCreators] = useState(true); - const [agents, setAgents] = useState<any[]>([]); - const [creators, setCreators] = useState<any[]>([]); + const [agents, setAgents] = useState<StoreAgent[]>([]); + const [creators, setCreators] = useState<Creator[]>([]); const [isLoading, setIsLoading] = useState(true); const api = useBackendAPI(); diff --git a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/dashboard/page.tsx b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/dashboard/page.tsx index e2cface85c..5c86d99f21 100644 --- a/autogpt_platform/frontend/src/app/(platform)/profile/(user)/dashboard/page.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/profile/(user)/dashboard/page.tsx @@ -14,7 +14,7 @@ import { import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; -export default function Page({}: {}) { +export default function Page() { const { supabase } = useSupabase(); const api = useBackendAPI(); const [submissions, setSubmissions] = useState<StoreSubmissionsResponse>(); diff --git a/autogpt_platform/frontend/src/app/layout.tsx b/autogpt_platform/frontend/src/app/layout.tsx index 9df9c1b671..d99e0b15d2 100644 --- a/autogpt_platform/frontend/src/app/layout.tsx +++ b/autogpt_platform/frontend/src/app/layout.tsx @@ -1,4 +1,4 @@ -import React, { Suspense } from "react"; +import React from "react"; import type { Metadata } from "next"; import { fonts } from "@/components/styles/fonts"; @@ -8,6 +8,7 @@ import { Toaster } from "@/components/ui/toaster"; import { Providers } from "@/app/providers"; import TallyPopupSimple from "@/components/TallyPopup"; import { GoogleAnalytics } from "@/components/analytics/google-analytics"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; export const metadata: Metadata = { title: "AutoGPT Platform", @@ -41,6 +42,14 @@ export default async function RootLayout({ <div className="flex min-h-screen flex-col items-stretch justify-items-stretch"> {children} <TallyPopupSimple /> + + {/* React Query DevTools is only available in development */} + {process.env.NEXT_PUBLIC_REACT_QUERY_DEVTOOL && ( + <ReactQueryDevtools + initialIsOpen={false} + buttonPosition={"bottom-left"} + /> + )} </div> <Toaster /> </Providers> diff --git a/autogpt_platform/frontend/src/app/providers.tsx b/autogpt_platform/frontend/src/app/providers.tsx index db9725d3f2..45f9fa5cc3 100644 --- a/autogpt_platform/frontend/src/app/providers.tsx +++ b/autogpt_platform/frontend/src/app/providers.tsx @@ -8,19 +8,24 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import CredentialsProvider from "@/components/integrations/credentials-provider"; import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider"; import OnboardingProvider from "@/components/onboarding/onboarding-provider"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { getQueryClient } from "@/lib/react-query/queryClient"; export function Providers({ children, ...props }: ThemeProviderProps) { + const queryClient = getQueryClient(); return ( - <NextThemesProvider {...props}> - <BackendAPIProvider> - <CredentialsProvider> - <LaunchDarklyProvider> - <OnboardingProvider> - <TooltipProvider>{children}</TooltipProvider> - </OnboardingProvider> - </LaunchDarklyProvider> - </CredentialsProvider> - </BackendAPIProvider> - </NextThemesProvider> + <QueryClientProvider client={queryClient}> + <NextThemesProvider {...props}> + <BackendAPIProvider> + <CredentialsProvider> + <LaunchDarklyProvider> + <OnboardingProvider> + <TooltipProvider>{children}</TooltipProvider> + </OnboardingProvider> + </LaunchDarklyProvider> + </CredentialsProvider> + </BackendAPIProvider> + </NextThemesProvider> + </QueryClientProvider> ); } diff --git a/autogpt_platform/frontend/src/components/ConnectionLine.tsx b/autogpt_platform/frontend/src/components/ConnectionLine.tsx index 2111a7b253..0a790aedd4 100644 --- a/autogpt_platform/frontend/src/components/ConnectionLine.tsx +++ b/autogpt_platform/frontend/src/components/ConnectionLine.tsx @@ -17,8 +17,8 @@ export default function ConnectionLine<NodeType extends Node>({ }: ConnectionLineComponentProps<NodeType>) { const sourceX = fromPosition === Position.Right - ? fromX + (fromHandle?.width! / 2 - 5) - : fromX - (fromHandle?.width! / 2 - 5); + ? fromX + ((fromHandle?.width ?? 0) / 2 - 5) + : fromX - ((fromHandle?.width ?? 0) / 2 - 5); const [path] = getBezierPath({ sourceX: sourceX, diff --git a/autogpt_platform/frontend/src/components/CustomEdge.tsx b/autogpt_platform/frontend/src/components/CustomEdge.tsx index bb620b2e6f..ecc9249fb2 100644 --- a/autogpt_platform/frontend/src/components/CustomEdge.tsx +++ b/autogpt_platform/frontend/src/components/CustomEdge.tsx @@ -93,7 +93,7 @@ export function CustomEdge({ return; } - const beadUp = data?.beadUp!; + const beadUp: number = data?.beadUp ?? 0; // Add beads setBeads(({ beads, created, destroyed }) => { @@ -114,10 +114,8 @@ export function CustomEdge({ const newBeads = beads .map((bead) => ({ ...bead })) .filter((bead, index) => { - const beadDown = data?.beadDown!; - - // Remove always one less bead in case of static edge, so it stays at the connection point - const removeCount = beadDown - destroyed - (data?.isStatic ? 1 : 0); + const beadDown: number = data?.beadDown ?? 0; + const removeCount = beadDown - destroyed; if (bead.t >= bead.targetT && index < removeCount) { destroyedCount++; return false; @@ -153,10 +151,8 @@ export function CustomEdge({ }; }) .filter((bead, index) => { - const beadDown = data?.beadDown!; - - // Remove always one less bead in case of static edge, so it stays at the connection point - const removeCount = beadDown - destroyed - (data?.isStatic ? 1 : 0); + const beadDown: number = data?.beadDown ?? 0; + const removeCount = beadDown - destroyed; if (bead.t >= bead.targetT && index < removeCount) { destroyedCount++; return false; diff --git a/autogpt_platform/frontend/src/components/CustomNode.tsx b/autogpt_platform/frontend/src/components/CustomNode.tsx index 0658ba9f7b..aa87ba30e2 100644 --- a/autogpt_platform/frontend/src/components/CustomNode.tsx +++ b/autogpt_platform/frontend/src/components/CustomNode.tsx @@ -27,6 +27,7 @@ import { cn, getValue, hasNonNullNonObjectValue, + isObject, parseKeys, setNestedProperty, } from "@/lib/utils"; @@ -94,13 +95,7 @@ export type CustomNodeData = { export type CustomNode = XYNode<CustomNodeData, "custom">; export const CustomNode = React.memo( - function CustomNode({ - data, - id, - width, - height, - selected, - }: NodeProps<CustomNode>) { + function CustomNode({ data, id, height, selected }: NodeProps<CustomNode>) { const [isOutputOpen, setIsOutputOpen] = useState( data.isOutputOpen || false, ); @@ -198,10 +193,6 @@ export const CustomNode = React.memo( [id, updateNodeData], ); - const toggleOutput = (checked: boolean) => { - setIsOutputOpen(checked); - }; - const toggleAdvancedSettings = (checked: boolean) => { setIsAdvancedOpen(checked); }; @@ -255,7 +246,7 @@ export const CustomNode = React.memo( nodeType: BlockUIType, ) => { if (!schema?.properties) return null; - let keys = Object.entries(schema.properties); + const keys = Object.entries(schema.properties); switch (nodeType) { case BlockUIType.NOTE: // For NOTE blocks, don't render any input handles @@ -435,8 +426,15 @@ export const CustomNode = React.memo( if (activeKey) { try { const parsedValue = JSON.parse(value); - handleInputChange(activeKey, parsedValue); - } catch (error) { + // Validate that the parsed value is safe before using it + if (isObject(parsedValue) || Array.isArray(parsedValue)) { + handleInputChange(activeKey, parsedValue); + } else { + // For primitive values, use the original string + handleInputChange(activeKey, value); + } + } catch { + // If JSON parsing fails, treat as plain text handleInputChange(activeKey, value); } } diff --git a/autogpt_platform/frontend/src/components/DataTable.tsx b/autogpt_platform/frontend/src/components/DataTable.tsx index aba62af6c2..b7bb83c80a 100644 --- a/autogpt_platform/frontend/src/components/DataTable.tsx +++ b/autogpt_platform/frontend/src/components/DataTable.tsx @@ -1,6 +1,8 @@ -import React from "react"; import { beautifyString } from "@/lib/utils"; +import { Clipboard } from "lucide-react"; +import React from "react"; import { Button } from "./ui/button"; +import { ContentRenderer } from "./ui/render"; import { Table, TableBody, @@ -9,9 +11,7 @@ import { TableHeader, TableRow, } from "./ui/table"; -import { Clipboard } from "lucide-react"; import { useToast } from "./ui/use-toast"; -import { ContentRenderer } from "./ui/render"; type DataTableProps = { title?: string; @@ -25,7 +25,6 @@ export default function DataTable({ data, }: DataTableProps) { const { toast } = useToast(); - const maxChars = 100; const copyData = (pin: string, data: string) => { navigator.clipboard.writeText(data).then(() => { diff --git a/autogpt_platform/frontend/src/components/Flow.tsx b/autogpt_platform/frontend/src/components/Flow.tsx index ece3239580..345fc60b5a 100644 --- a/autogpt_platform/frontend/src/components/Flow.tsx +++ b/autogpt_platform/frontend/src/components/Flow.tsx @@ -90,9 +90,7 @@ const FlowEditor: React.FC<{ } = useReactFlow<CustomNode, CustomEdge>(); const [nodeId, setNodeId] = useState<number>(1); const [isAnyModalOpen, setIsAnyModalOpen] = useState(false); - const [visualizeBeads, setVisualizeBeads] = useState< - "no" | "static" | "animate" - >("animate"); + const [visualizeBeads] = useState<"no" | "static" | "animate">("animate"); const [flowExecutionID, setFlowExecutionID] = useState< GraphExecutionID | undefined >(); @@ -366,10 +364,7 @@ const FlowEditor: React.FC<{ replaceEdges = edgeChanges.filter( (change) => change.type === "replace", ), - removedEdges = edgeChanges.filter((change) => change.type === "remove"), - selectedEdges = edgeChanges.filter( - (change) => change.type === "select", - ); + removedEdges = edgeChanges.filter((change) => change.type === "remove"); if (addedEdges.length > 0 || removedEdges.length > 0) { setNodes((nds) => { diff --git a/autogpt_platform/frontend/src/components/NodeOutputs.tsx b/autogpt_platform/frontend/src/components/NodeOutputs.tsx index fcedf1f408..1a6ad26e0a 100644 --- a/autogpt_platform/frontend/src/components/NodeOutputs.tsx +++ b/autogpt_platform/frontend/src/components/NodeOutputs.tsx @@ -26,15 +26,23 @@ export default function NodeOutputs({ <div className="mt-2"> <strong className="mr-2">Data:</strong> <div className="mt-1"> - {dataArray.map((item, index) => ( + {dataArray.slice(0, 10).map((item, index) => ( <React.Fragment key={index}> <ContentRenderer value={item} truncateLongData={truncateLongData} /> - {index < dataArray.length - 1 && ", "} + {index < Math.min(dataArray.length, 10) - 1 && ", "} </React.Fragment> ))} + {dataArray.length > 10 && ( + <span style={{ color: "#888" }}> + <br /> + <b>⋮</b> + <br /> + <span>and {dataArray.length - 10} more</span> + </span> + )} </div> <Separator.Root className="my-4 h-[1px] bg-gray-300" /> </div> diff --git a/autogpt_platform/frontend/src/components/OttoChatWidget.tsx b/autogpt_platform/frontend/src/components/OttoChatWidget.tsx index 3e111165a8..3eaf75951d 100644 --- a/autogpt_platform/frontend/src/components/OttoChatWidget.tsx +++ b/autogpt_platform/frontend/src/components/OttoChatWidget.tsx @@ -212,7 +212,7 @@ export default function OttoChatWidget({ <p className="mb-2 last:mb-0">{children}</p> ), code(props) { - const { children, className, node, ...rest } = props; + const { children, className, node: _, ...rest } = props; const match = /language-(\w+)/.exec(className || ""); return match ? ( <pre className="overflow-x-auto rounded-md bg-muted-foreground/20 p-3"> diff --git a/autogpt_platform/frontend/src/components/RunnerUIWrapper.tsx b/autogpt_platform/frontend/src/components/RunnerUIWrapper.tsx index 5fef2152f2..2f72754205 100644 --- a/autogpt_platform/frontend/src/components/RunnerUIWrapper.tsx +++ b/autogpt_platform/frontend/src/components/RunnerUIWrapper.tsx @@ -104,7 +104,10 @@ const RunnerUIWrapper = forwardRef<RunnerUIWrapperRef, RunnerUIWrapperProps>( (node.data.hardcodedValues as any).description || "Output from the agent", }, - result: (node.data.executionResults as any)?.at(-1)?.data?.output, + result: + (node.data.executionResults as any) + ?.map((result: any) => result?.data?.output) + .join("\n--\n") || "No output yet", }) satisfies BlockOutput, ); diff --git a/autogpt_platform/frontend/src/components/SchemaTooltip.tsx b/autogpt_platform/frontend/src/components/SchemaTooltip.tsx index 313b55cb6b..b4010287bc 100644 --- a/autogpt_platform/frontend/src/components/SchemaTooltip.tsx +++ b/autogpt_platform/frontend/src/components/SchemaTooltip.tsx @@ -22,7 +22,7 @@ const SchemaTooltip: React.FC<{ description?: string }> = ({ description }) => { <TooltipContent className="tooltip-content max-w-xs bg-white text-gray-900 dark:bg-gray-800 dark:text-gray-100"> <ReactMarkdown components={{ - a: ({ node, ...props }) => ( + a: ({ node: _, ...props }) => ( <a target="_blank" className="text-blue-400 underline dark:text-blue-300" diff --git a/autogpt_platform/frontend/src/components/admin/marketplace/expandable-row.tsx b/autogpt_platform/frontend/src/components/admin/marketplace/expandable-row.tsx index bc42c43686..569bdc7eaf 100644 --- a/autogpt_platform/frontend/src/components/admin/marketplace/expandable-row.tsx +++ b/autogpt_platform/frontend/src/components/admin/marketplace/expandable-row.tsx @@ -9,9 +9,8 @@ import { TableHead, TableBody, } from "@/components/ui/table"; -import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; -import { ChevronDown, ChevronRight, ExternalLink } from "lucide-react"; +import { ChevronDown, ChevronRight } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import { type StoreListingWithVersions, @@ -19,7 +18,6 @@ import { SubmissionStatus, } from "@/lib/autogpt-server-api/types"; import { ApproveRejectButtons } from "./approve-reject-buttons"; -import { downloadAsAdmin } from "@/app/(platform)/admin/marketplace/actions"; import { DownloadAgentAdminButton } from "./download-agent-button"; // Moved the getStatusBadge function into the client component diff --git a/autogpt_platform/frontend/src/components/admin/marketplace/search-filter-form.tsx b/autogpt_platform/frontend/src/components/admin/marketplace/search-filter-form.tsx index 4d406fab93..73da6da431 100644 --- a/autogpt_platform/frontend/src/components/admin/marketplace/search-filter-form.tsx +++ b/autogpt_platform/frontend/src/components/admin/marketplace/search-filter-form.tsx @@ -15,7 +15,6 @@ import { import { SubmissionStatus } from "@/lib/autogpt-server-api/types"; export function SearchAndFilterAdminMarketplace({ - initialStatus, initialSearch, }: { initialStatus?: SubmissionStatus; diff --git a/autogpt_platform/frontend/src/components/admin/spending/add-money-button.tsx b/autogpt_platform/frontend/src/components/admin/spending/add-money-button.tsx index 2f1083eb7d..ee84464df0 100644 --- a/autogpt_platform/frontend/src/components/admin/spending/add-money-button.tsx +++ b/autogpt_platform/frontend/src/components/admin/spending/add-money-button.tsx @@ -15,7 +15,6 @@ import { Textarea } from "@/components/ui/textarea"; import { Input } from "@/components/ui/input"; import { useRouter } from "next/navigation"; import { addDollars } from "@/app/(platform)/admin/spending/actions"; -import useCredits from "@/hooks/useCredits"; export function AdminAddMoneyButton({ userId, @@ -36,8 +35,6 @@ export function AdminAddMoneyButton({ defaultAmount ? Math.abs(defaultAmount / 100).toFixed(2) : "1.00", ); - const { formatCredits } = useCredits(); - const handleApproveSubmit = async (formData: FormData) => { setIsAddMoneyDialogOpen(false); try { diff --git a/autogpt_platform/frontend/src/components/admin/spending/admin-grant-history-data-table.tsx b/autogpt_platform/frontend/src/components/admin/spending/admin-grant-history-data-table.tsx index 818d385d04..80695b803c 100644 --- a/autogpt_platform/frontend/src/components/admin/spending/admin-grant-history-data-table.tsx +++ b/autogpt_platform/frontend/src/components/admin/spending/admin-grant-history-data-table.tsx @@ -48,7 +48,7 @@ export async function AdminUserGrantHistory({ const isPurchased = type === CreditTransactionType.TOP_UP; const isSpent = type === CreditTransactionType.USAGE; - let displayText = type; + const displayText = type; let bgColor = ""; if (isGrant) { diff --git a/autogpt_platform/frontend/src/components/admin/spending/search-filter-form.tsx b/autogpt_platform/frontend/src/components/admin/spending/search-filter-form.tsx index 5e0160cbdd..f301461071 100644 --- a/autogpt_platform/frontend/src/components/admin/spending/search-filter-form.tsx +++ b/autogpt_platform/frontend/src/components/admin/spending/search-filter-form.tsx @@ -15,7 +15,6 @@ import { } from "@/components/ui/select"; export function SearchAndFilterAdminSpending({ - initialStatus, initialSearch, }: { initialStatus?: CreditTransactionType; @@ -74,7 +73,7 @@ export function SearchAndFilterAdminSpending({ <Select value={selectedStatus} - onValueChange={(value) => { + onValueChange={(value: string) => { setSelectedStatus(value); const params = new URLSearchParams(searchParams.toString()); if (value === "ALL") { diff --git a/autogpt_platform/frontend/src/components/agptui/AgentImages.stories.tsx b/autogpt_platform/frontend/src/components/agptui/AgentImages.stories.tsx index e752e450d0..8e0e3ba4d8 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentImages.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentImages.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { AgentImages } from "./AgentImages"; const meta = { - title: "AGPT UI/Agent Images", + title: "Legacy/Agent Images", component: AgentImages, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/AgentInfo.stories.tsx b/autogpt_platform/frontend/src/components/agptui/AgentInfo.stories.tsx index 2bd0d59503..06ecaef2f0 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentInfo.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentInfo.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { AgentInfo } from "./AgentInfo"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/Agent Info", + title: "Legacy/Agent Info", component: AgentInfo, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/AgentTable.stories.tsx b/autogpt_platform/frontend/src/components/agptui/AgentTable.stories.tsx index b4a0d80fdc..992497edeb 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentTable.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentTable.stories.tsx @@ -1,11 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { AgentTable } from "./AgentTable"; import { AgentTableRowProps } from "./AgentTableRow"; -import { userEvent, within, expect } from "@storybook/test"; -import { StatusType } from "./Status"; +import { userEvent, within, expect } from "storybook/test"; const meta: Meta<typeof AgentTable> = { - title: "AGPT UI/Agent Table", + title: "Legacy/Agent Table", component: AgentTable, tags: ["autodocs"], }; @@ -104,6 +103,6 @@ export const EmptyTableTest: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); const emptyMessage = canvas.getByText("No agents found"); - expect(emptyMessage).toBeTruthy(); + await expect(emptyMessage).toBeTruthy(); }, }; diff --git a/autogpt_platform/frontend/src/components/agptui/AgentTable.tsx b/autogpt_platform/frontend/src/components/agptui/AgentTable.tsx index ba600c02dc..dcba564424 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentTable.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentTable.tsx @@ -90,7 +90,7 @@ export const AgentTable: React.FC<AgentTableProps> = ({ {/* Table body */} {agents.length > 0 ? ( <div className="flex flex-col"> - {agents.map((agent, index) => ( + {agents.map((agent) => ( <div key={agent.id} className="md:block"> <AgentTableRow {...agent} diff --git a/autogpt_platform/frontend/src/components/agptui/AgentTableCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/AgentTableCard.stories.tsx index babacc26d2..b686e5c900 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentTableCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentTableCard.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { AgentTableCard } from "./AgentTableCard"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; import { type StatusType } from "./Status"; const meta: Meta<typeof AgentTableCard> = { - title: "AGPT UI/Agent Table Card", + title: "Legacy/Agent Table Card", component: AgentTableCard, tags: ["autodocs"], }; diff --git a/autogpt_platform/frontend/src/components/agptui/AgentTableCard.tsx b/autogpt_platform/frontend/src/components/agptui/AgentTableCard.tsx index f984cf433d..1daa66cd53 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentTableCard.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentTableCard.tsx @@ -32,7 +32,6 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({ status, runs, rating, - id, onEditSubmission, }) => { const onEdit = () => { diff --git a/autogpt_platform/frontend/src/components/agptui/BecomeACreator.stories.tsx b/autogpt_platform/frontend/src/components/agptui/BecomeACreator.stories.tsx index 763686f3e0..0f5206c878 100644 --- a/autogpt_platform/frontend/src/components/agptui/BecomeACreator.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/BecomeACreator.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { BecomeACreator } from "./BecomeACreator"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/Become A Creator", + title: "Legacy/Become A Creator", component: BecomeACreator, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.stories.tsx b/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.stories.tsx index c94d6040a6..55ff95498d 100644 --- a/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { BreadCrumbs } from "./BreadCrumbs"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/BreadCrumbs", + title: "Legacy/BreadCrumbs", component: BreadCrumbs, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.tsx b/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.tsx index 6188f454d2..069f7b3e3a 100644 --- a/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.tsx +++ b/autogpt_platform/frontend/src/components/agptui/BreadCrumbs.tsx @@ -1,6 +1,5 @@ import * as React from "react"; import Link from "next/link"; -import { IconLeftArrow, IconRightArrow } from "@/components/ui/icons"; interface BreadcrumbItem { name: string; diff --git a/autogpt_platform/frontend/src/components/agptui/Button.stories.tsx b/autogpt_platform/frontend/src/components/agptui/Button.stories.tsx index 42d3c55e4e..6437c9e173 100644 --- a/autogpt_platform/frontend/src/components/agptui/Button.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Button.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Button } from "./Button"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Button", + title: "Legacy/Button", component: Button, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/CreatorCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/CreatorCard.stories.tsx index ee2927545c..5f56309e6a 100644 --- a/autogpt_platform/frontend/src/components/agptui/CreatorCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/CreatorCard.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { CreatorCard } from "./CreatorCard"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/Creator Card", + title: "Legacy/Creator Card", component: CreatorCard, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/CreatorInfoCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/CreatorInfoCard.stories.tsx index 820fcbb87b..d22715c9cb 100644 --- a/autogpt_platform/frontend/src/components/agptui/CreatorInfoCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/CreatorInfoCard.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { CreatorInfoCard } from "./CreatorInfoCard"; const meta = { - title: "AGPT UI/Creator Info Card", + title: "Legacy/Creator Info Card", component: CreatorInfoCard, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/CreatorLinks.stories.tsx b/autogpt_platform/frontend/src/components/agptui/CreatorLinks.stories.tsx index ad935f1dfb..4f74571142 100644 --- a/autogpt_platform/frontend/src/components/agptui/CreatorLinks.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/CreatorLinks.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { CreatorLinks } from "./CreatorLinks"; const meta = { - title: "AGPT UI/Creator Links", + title: "Legacy/Creator Links", component: CreatorLinks, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/FeaturedStoreCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/FeaturedStoreCard.stories.tsx index 5a237ee5bd..e1ff9ac4fc 100644 --- a/autogpt_platform/frontend/src/components/agptui/FeaturedStoreCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/FeaturedStoreCard.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { userEvent, within } from "storybook/test"; import { FeaturedAgentCard } from "./FeaturedAgentCard"; -import { userEvent, within } from "@storybook/test"; const meta = { - title: "AGPT UI/Featured Store Card", + title: "Legacy/Featured Store Card", component: FeaturedAgentCard, parameters: { layout: { @@ -35,6 +35,7 @@ type Story = StoryObj<typeof meta>; export const Default: Story = { args: { agent: { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "Personalized Morning Coffee Newsletter example of three lines", sub_heading: @@ -57,6 +58,7 @@ export const Default: Story = { export const WithInteraction: Story = { args: { agent: { + updated_at: "2024-01-10T15:30:00.000Z", slug: "", agent_name: "AI Writing Assistant", sub_heading: "Enhance your writing", diff --git a/autogpt_platform/frontend/src/components/agptui/FilterChips.stories.tsx b/autogpt_platform/frontend/src/components/agptui/FilterChips.stories.tsx index 8ae1360629..5258ad67ef 100644 --- a/autogpt_platform/frontend/src/components/agptui/FilterChips.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/FilterChips.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { FilterChips } from "./FilterChips"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Filter Chips", + title: "Legacy/Filter Chips", component: FilterChips, parameters: { layout: "centered", @@ -46,7 +46,7 @@ export const WithSelectedFilters: Story = { badges: defaultBadges, multiSelect: true, }, - play: async ({ canvasElement, args }) => { + play: async ({ canvasElement }) => { const canvas = within(canvasElement); const marketingChip = canvas.getByText("Marketing").parentElement; const salesChip = canvas.getByText("Sales").parentElement; @@ -70,7 +70,7 @@ export const WithFilterChangeCallback: Story = { console.log("Selected filters:", selectedFilters); }, }, - play: async ({ canvasElement, args }) => { + play: async ({ canvasElement }) => { const canvas = within(canvasElement); const salesChip = canvas.getByText("Sales"); const marketingChip = canvas.getByText("Marketing"); @@ -104,7 +104,7 @@ export const SingleSelectBehavior: Story = { badges: defaultBadges, multiSelect: false, }, - play: async ({ canvasElement, args }) => { + play: async ({ canvasElement }) => { const canvas = within(canvasElement); const salesChip = canvas.getByText("Sales").parentElement; const marketingChip = canvas.getByText("Marketing").parentElement; diff --git a/autogpt_platform/frontend/src/components/agptui/MobileNavBar.stories.tsx b/autogpt_platform/frontend/src/components/agptui/MobileNavBar.stories.tsx index 09848688b3..55d836d409 100644 --- a/autogpt_platform/frontend/src/components/agptui/MobileNavBar.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/MobileNavBar.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { MobileNavBar } from "./MobileNavBar"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; import { IconType } from "../ui/icons"; const meta = { - title: "AGPT UI/Mobile Nav Bar", + title: "Legacy/Mobile Nav Bar", component: MobileNavBar, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/Navbar.stories.tsx b/autogpt_platform/frontend/src/components/agptui/Navbar.stories.tsx index 500466c3a9..4c0b2243d1 100644 --- a/autogpt_platform/frontend/src/components/agptui/Navbar.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Navbar.stories.tsx @@ -1,36 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Navbar } from "./Navbar"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; import { IconType } from "../ui/icons"; -import { ProfileDetails } from "@/lib/autogpt-server-api/types"; -// You can't import this here, jest is not available in storybook and will crash it -// import { jest } from "@jest/globals"; - -// Mock the API responses -const mockProfileData: ProfileDetails = { - name: "John Doe", - username: "johndoe", - description: "", - links: [], - avatar_url: "https://avatars.githubusercontent.com/u/123456789?v=4", -}; - -const mockCreditData = { - credits: 1500, -}; - -// Mock the API module -// jest.mock("@/lib/autogpt-server-api", () => { -// return function () { -// return { -// getStoreProfile: () => Promise.resolve(mockProfileData), -// getUserCredit: () => Promise.resolve(mockCreditData), -// }; -// }; -// }); const meta = { - title: "AGPT UI/Navbar", + title: "Legacy/Navbar", component: Navbar, parameters: { layout: "fullscreen", diff --git a/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.stories.tsx b/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.stories.tsx index 32d4a003cb..2c43c58e57 100644 --- a/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { ProfileInfoForm } from "./ProfileInfoForm"; const meta: Meta<typeof ProfileInfoForm> = { - title: "AGPT UI/Profile/Profile Info Form", + title: "Legacy/Profile/Profile Info Form", component: ProfileInfoForm, parameters: { layout: "fullscreen", diff --git a/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenu.stories.tsx b/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenu.stories.tsx index 7af1dc295b..d86a23d322 100644 --- a/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenu.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenu.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { ProfilePopoutMenu } from "./ProfilePopoutMenu"; -import { userEvent, within } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; import { IconType } from "../ui/icons"; const meta = { - title: "AGPT UI/Profile Popout Menu", + title: "Legacy/Profile Popout Menu", component: ProfilePopoutMenu, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenuLogoutButton.tsx b/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenuLogoutButton.tsx index ea2b4473b9..293155d451 100644 --- a/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenuLogoutButton.tsx +++ b/autogpt_platform/frontend/src/components/agptui/ProfilePopoutMenuLogoutButton.tsx @@ -1,12 +1,12 @@ "use client"; -import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; import { IconLogOut } from "@/components/ui/icons"; +import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; +import { cn } from "@/lib/utils"; +import * as Sentry from "@sentry/nextjs"; +import { useRouter } from "next/navigation"; import { useTransition } from "react"; import { LoadingSpinner } from "../ui/loading"; -import { cn } from "@/lib/utils"; -import { useRouter } from "next/navigation"; import { toast } from "../ui/use-toast"; -import * as Sentry from "@sentry/nextjs"; export function ProfilePopoutMenuLogoutButton() { const router = useRouter(); @@ -16,7 +16,7 @@ export function ProfilePopoutMenuLogoutButton() { function handleLogout() { startTransition(async () => { try { - await supabase.logOut({ scope: "global" }); + await supabase.logOut(); router.refresh(); } catch (e) { Sentry.captureException(e); diff --git a/autogpt_platform/frontend/src/components/agptui/PublishAgentAwaitingReview.stories.tsx b/autogpt_platform/frontend/src/components/agptui/PublishAgentAwaitingReview.stories.tsx index a3d807d2e3..4302af9009 100644 --- a/autogpt_platform/frontend/src/components/agptui/PublishAgentAwaitingReview.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/PublishAgentAwaitingReview.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { PublishAgentAwaitingReview } from "./PublishAgentAwaitingReview"; const meta: Meta<typeof PublishAgentAwaitingReview> = { - title: "AGPT UI/Publish Agent Awaiting Review", + title: "Legacy/Publish Agent Awaiting Review", component: PublishAgentAwaitingReview, tags: ["autodocs"], parameters: { diff --git a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelect.stories.tsx b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelect.stories.tsx index de12c560b0..c116e66db9 100644 --- a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelect.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelect.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Agent, PublishAgentSelect } from "./PublishAgentSelect"; const meta: Meta<typeof PublishAgentSelect> = { - title: "AGPT UI/Publish Agent Select", + title: "Legacy/Publish Agent Select", component: PublishAgentSelect, tags: ["autodocs"], }; diff --git a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.stories.tsx b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.stories.tsx index f48f849043..621af7a8c2 100644 --- a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { PublishAgentInfo } from "./PublishAgentSelectInfo"; const meta: Meta<typeof PublishAgentInfo> = { - title: "AGPT UI/Publish Agent Info", + title: "Legacy/Publish Agent Info", component: PublishAgentInfo, tags: ["autodocs"], decorators: [ diff --git a/autogpt_platform/frontend/src/components/agptui/RatingCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/RatingCard.stories.tsx index c122b7f566..7e2b70c443 100644 --- a/autogpt_platform/frontend/src/components/agptui/RatingCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/RatingCard.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { RatingCard } from "./RatingCard"; const meta = { - title: "AGPT UI/RatingCard", + title: "Legacy/RatingCard", component: RatingCard, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/SearchBar.stories.tsx b/autogpt_platform/frontend/src/components/agptui/SearchBar.stories.tsx index a807a1d1b8..c5b1df8a7b 100644 --- a/autogpt_platform/frontend/src/components/agptui/SearchBar.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/SearchBar.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { SearchBar } from "./SearchBar"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Search Bar", + title: "Legacy/Search Bar", component: SearchBar, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/Sidebar.stories.tsx b/autogpt_platform/frontend/src/components/agptui/Sidebar.stories.tsx index 24a10df1ab..e59e54c384 100644 --- a/autogpt_platform/frontend/src/components/agptui/Sidebar.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Sidebar.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Sidebar } from "./Sidebar"; const meta = { - title: "AGPT UI/Sidebar", + title: "Legacy/Sidebar", component: Sidebar, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/Status.stories.tsx b/autogpt_platform/frontend/src/components/agptui/Status.stories.tsx index 390dc0b501..cdfb38540d 100644 --- a/autogpt_platform/frontend/src/components/agptui/Status.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Status.stories.tsx @@ -1,8 +1,8 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Status, StatusType } from "./Status"; const meta = { - title: "AGPT UI/Status", + title: "Legacy/Status", component: Status, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/StoreCard.stories.tsx b/autogpt_platform/frontend/src/components/agptui/StoreCard.stories.tsx index 48eb7fdfa9..956b25b8b2 100644 --- a/autogpt_platform/frontend/src/components/agptui/StoreCard.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/StoreCard.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { StoreCard } from "./StoreCard"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/StoreCard", + title: "Legacy/StoreCard", component: StoreCard, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/StoreCard.tsx b/autogpt_platform/frontend/src/components/agptui/StoreCard.tsx index 57919dc2eb..bc024713f9 100644 --- a/autogpt_platform/frontend/src/components/agptui/StoreCard.tsx +++ b/autogpt_platform/frontend/src/components/agptui/StoreCard.tsx @@ -32,7 +32,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({ return ( <div - className="flex h-[27rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl bg-white transition-all duration-300 hover:shadow-lg dark:bg-transparent dark:hover:shadow-gray-700" + className="flex h-[27rem] w-full max-w-md cursor-pointer flex-col items-start rounded-3xl bg-background transition-all duration-300 hover:shadow-lg dark:hover:shadow-gray-700" onClick={handleClick} data-testid="store-card" role="button" diff --git a/autogpt_platform/frontend/src/components/agptui/ThemeToggle.tsx b/autogpt_platform/frontend/src/components/agptui/ThemeToggle.tsx index 515a98cda1..2b9d75c6e4 100644 --- a/autogpt_platform/frontend/src/components/agptui/ThemeToggle.tsx +++ b/autogpt_platform/frontend/src/components/agptui/ThemeToggle.tsx @@ -3,7 +3,6 @@ import * as React from "react"; import { useTheme } from "next-themes"; import { IconMoon, IconSun } from "@/components/ui/icons"; -import { Button } from "./Button"; export function ThemeToggle() { const { theme, setTheme } = useTheme(); diff --git a/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx b/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx index 756df29bc0..74cbcc0337 100644 --- a/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx +++ b/autogpt_platform/frontend/src/components/agptui/WalletRefill.tsx @@ -60,7 +60,6 @@ export default function WalletRefill() { // Pre-fill the auto-refill form with existing values useEffect(() => { - const values = autoRefillForm.getValues(); if ( autoTopUpConfig && autoTopUpConfig.amount > 0 && diff --git a/autogpt_platform/frontend/src/components/agptui/composite/APIKeySection.tsx b/autogpt_platform/frontend/src/components/agptui/composite/APIKeySection.tsx index 3eaa0a5270..1fa9db3cdc 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/APIKeySection.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/APIKeySection.tsx @@ -84,7 +84,7 @@ export function APIKeysSection() { setIsCreateOpen(false); setIsKeyDialogOpen(true); loadAPIKeys(); - } catch (error) { + } catch { toast({ title: "Error", description: "Failed to create AutoGPT Platform API key", @@ -109,7 +109,7 @@ export function APIKeysSection() { description: "AutoGPT Platform API key revoked successfully", }); loadAPIKeys(); - } catch (error) { + } catch { toast({ title: "Error", description: "Failed to revoke AutoGPT Platform API key", @@ -168,7 +168,7 @@ export function APIKeysSection() { <Checkbox id={permission} checked={selectedPermissions.includes(permission)} - onCheckedChange={(checked) => { + onCheckedChange={(checked: boolean) => { setSelectedPermissions( checked ? [...selectedPermissions, permission] diff --git a/autogpt_platform/frontend/src/components/agptui/composite/AgentsSection.stories.tsx b/autogpt_platform/frontend/src/components/agptui/composite/AgentsSection.stories.tsx index 7e23f22e6c..a9313ce9d8 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/AgentsSection.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/AgentsSection.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { Agent, AgentsSection } from "./AgentsSection"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Composite/Agents Section", + title: "Legacy/Composite/Agents Section", component: AgentsSection, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedCreators.stories.tsx b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedCreators.stories.tsx index 96c184ae76..cec11cb00c 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedCreators.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedCreators.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { FeaturedCreators } from "./FeaturedCreators"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Composite/Featured Creators", + title: "Legacy/Composite/Featured Creators", component: FeaturedCreators, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.stories.tsx b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.stories.tsx index d494a428fe..dcb2aa81a8 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { FeaturedSection } from "./FeaturedSection"; -import { userEvent, within } from "@storybook/test"; import { StoreAgent } from "@/lib/autogpt-server-api"; +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { userEvent, within } from "storybook/test"; +import { FeaturedSection } from "./FeaturedSection"; const meta = { - title: "AGPT UI/Composite/Featured Agents", + title: "Legacy/Composite/Featured Agents", component: FeaturedSection, parameters: { layout: { @@ -24,6 +24,7 @@ type Story = StoryObj<typeof meta>; const mockFeaturedAgents = [ { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "Personalized Morning Coffee Newsletter example of three lines", sub_heading: "Transform ideas into breathtaking images with this AI-powered Image Generator.", @@ -39,6 +40,7 @@ const mockFeaturedAgents = [ slug: "personalized-morning-coffee-newsletter", }, { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "Data Analyzer Lite", sub_heading: "Basic data analysis tool", creator: "DataTech", @@ -53,6 +55,7 @@ const mockFeaturedAgents = [ slug: "data-analyzer-lite", }, { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "CodeAssist AI", sub_heading: "Your AI coding companion", creator: "DevTools Co.", @@ -67,6 +70,7 @@ const mockFeaturedAgents = [ slug: "codeassist-ai", }, { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "MultiTasker", sub_heading: "All-in-one productivity suite", creator: "Productivity Plus", @@ -81,6 +85,7 @@ const mockFeaturedAgents = [ slug: "multitasker", }, { + updated_at: "2024-01-10T15:30:00.000Z", agent_name: "QuickTask", sub_heading: "Fast task automation", creator: "EfficientWorks", diff --git a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.tsx b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.tsx index 84c61295e5..43bfb8ca07 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/FeaturedSection.tsx @@ -27,7 +27,7 @@ interface FeaturedSectionProps { export const FeaturedSection: React.FC<FeaturedSectionProps> = ({ featuredAgents, }) => { - const [currentSlide, setCurrentSlide] = useState(0); + const [_, setCurrentSlide] = useState(0); const handlePrevSlide = useCallback(() => { setCurrentSlide((prev) => diff --git a/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.stories.tsx b/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.stories.tsx index 11b101bd6b..6a0add5dcc 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { HeroSection } from "./HeroSection"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within, expect } from "storybook/test"; const meta = { - title: "AGPT UI/Composite/Hero Section", + title: "Legacy/Composite/Hero Section", component: HeroSection, parameters: { layout: { diff --git a/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.tsx b/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.tsx index 955ba1830c..e666d9f267 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/HeroSection.tsx @@ -5,7 +5,6 @@ import { SearchBar } from "@/components/agptui/SearchBar"; import { FilterChips } from "@/components/agptui/FilterChips"; import { useRouter } from "next/navigation"; import { useOnboarding } from "@/components/onboarding/onboarding-provider"; -import { useSupabase } from "@/lib/supabase/hooks/useSupabase"; export const HeroSection: React.FC = () => { const router = useRouter(); diff --git a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.stories.tsx b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.stories.tsx index 9326edc3d7..c06f1274df 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.stories.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout"; -import { userEvent, within, expect } from "@storybook/test"; +import { userEvent, within } from "storybook/test"; const meta = { - title: "AGPT UI/Composite/Publish Agent Popout", + title: "Legacy/Composite/Publish Agent Popout", component: PublishAgentPopout, parameters: { layout: "centered", diff --git a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx index 55fc8e6791..6dc27ee29d 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx @@ -47,7 +47,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({ inputStep, ); const [myAgents, setMyAgents] = React.useState<MyAgentsResponse | null>(null); - const [selectedAgent, setSelectedAgent] = React.useState<string | null>(null); + const [_, setSelectedAgent] = React.useState<string | null>(null); const [initialData, setInitialData] = React.useState<PublishAgentInfoInitialData>({ agent_id: "", @@ -181,7 +181,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({ // Create store submission try { - const submission = await api.createStoreSubmission({ + await api.createStoreSubmission({ name: name, sub_heading: subHeading, description: description, diff --git a/autogpt_platform/frontend/src/components/analytics/google-analytics.tsx b/autogpt_platform/frontend/src/components/analytics/google-analytics.tsx index 0bebaca225..58d5f996b5 100644 --- a/autogpt_platform/frontend/src/components/analytics/google-analytics.tsx +++ b/autogpt_platform/frontend/src/components/analytics/google-analytics.tsx @@ -5,9 +5,9 @@ "use client"; -import { useEffect } from "react"; -import Script from "next/script"; import type { GAParams } from "@/types/google"; +import Script from "next/script"; +import { useEffect } from "react"; let currDataLayerName: string | undefined = undefined; @@ -31,7 +31,8 @@ export function GoogleAnalytics(props: GAParams) { <> <Script id="_custom-ga-init" - strategy="beforeInteractive" + // Using "afterInteractive" to avoid blocking the initial page rendering + strategy="afterInteractive" dangerouslySetInnerHTML={{ __html: ` window['${dataLayerName}'] = window['${dataLayerName}'] || []; @@ -44,7 +45,7 @@ export function GoogleAnalytics(props: GAParams) { /> <Script id="_custom-ga" - strategy="beforeInteractive" + strategy="afterInteractive" src="/gtag.js" nonce={nonce} /> @@ -52,15 +53,15 @@ export function GoogleAnalytics(props: GAParams) { ); } -export function sendGAEvent(..._args: any[]) { +export function sendGAEvent(...args: any[]) { if (currDataLayerName === undefined) { console.warn(`Custom GA: GA has not been initialized`); return; } - //@ts-ignore - if (window[currDataLayerName]) { - //@ts-ignore - window[currDataLayerName].push(arguments); + + const dataLayer = (window as any)[currDataLayerName]; + if (dataLayer) { + dataLayer.push(...args); } else { console.warn(`Custom GA: dataLayer ${currDataLayerName} does not exist`); } diff --git a/autogpt_platform/frontend/src/components/atoms/Button/Button.stories.tsx b/autogpt_platform/frontend/src/components/atoms/Button/Button.stories.tsx new file mode 100644 index 0000000000..9b3be42fe8 --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Button/Button.stories.tsx @@ -0,0 +1,526 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { Play, Plus } from "lucide-react"; +import { Button } from "./Button"; + +const meta: Meta<typeof Button> = { + title: "Atoms/Button", + tags: ["autodocs"], + component: Button, + parameters: { + layout: "centered", + docs: { + description: { + component: + "Button component with multiple variants and sizes based on our design system. Built on top of shadcn/ui button with custom styling.", + }, + }, + }, + argTypes: { + variant: { + control: "select", + options: [ + "primary", + "secondary", + "destructive", + "outline", + "ghost", + "loading", + ], + description: "Button style variant", + }, + size: { + control: "select", + options: ["small", "large", "icon"], + description: "Button size", + }, + loading: { + control: "boolean", + description: "Show loading spinner and disable button", + }, + disabled: { + control: "boolean", + description: "Disable the button", + }, + children: { + control: "text", + description: "Button content", + }, + }, + args: { + children: "Button", + variant: "primary", + size: "large", + loading: false, + disabled: false, + }, +}; + +export default meta; +type Story = StoryObj<typeof meta>; + +// Basic variants +export const Primary: Story = { + args: { + variant: "primary", + children: "Primary Button", + }, +}; + +export const Secondary: Story = { + args: { + variant: "secondary", + children: "Secondary Button", + }, +}; + +export const Destructive: Story = { + args: { + variant: "destructive", + children: "Delete", + }, +}; + +export const Outline: Story = { + args: { + variant: "outline", + children: "Outline Button", + }, +}; + +export const Ghost: Story = { + args: { + variant: "ghost", + children: "Ghost Button", + }, +}; + +export const Link: Story = { + args: { + variant: "link", + children: "Add to library", + }, +}; + +// Loading states +export const Loading: Story = { + args: { + loading: true, + children: "Processing...", + }, +}; + +export const LoadingSecondary: Story = { + args: { + variant: "secondary", + loading: true, + children: "Loading...", + }, +}; + +// Sizes +export const SmallButtons: Story = { + render: renderSmallButtons, +}; + +export const LargeButtons: Story = { + render: renderLargeButtons, +}; + +// With icons +export const WithLeftIcon: Story = { + args: { + variant: "primary", + leftIcon: <Play className="h-4 w-4" />, + children: "Play", + }, +}; + +export const WithRightIcon: Story = { + args: { + variant: "outline", + rightIcon: <Plus className="h-4 w-4" />, + children: "Add Item", + }, +}; + +export const IconOnly: Story = { + args: { + variant: "icon", + size: "icon", + children: <Plus className="h-4 w-4" />, + "aria-label": "Add item", + }, +}; + +// States +export const Disabled: Story = { + render: renderDisabledButtons, +}; + +// Complete showcase matching Figma design +export const AllVariants: Story = { + render: renderAllVariants, +}; + +// Render functions as function declarations +function renderSmallButtons() { + return ( + <div className="flex flex-wrap gap-4"> + <Button variant="primary" size="small"> + Primary + </Button> + <Button variant="secondary" size="small"> + Secondary + </Button> + <Button variant="destructive" size="small"> + Delete + </Button> + <Button variant="outline" size="small"> + Outline + </Button> + <Button variant="ghost" size="small"> + Ghost + </Button> + </div> + ); +} + +function renderLargeButtons() { + return ( + <div className="flex flex-wrap gap-4"> + <Button variant="primary" size="large"> + Primary + </Button> + <Button variant="secondary" size="large"> + Secondary + </Button> + <Button variant="destructive" size="large"> + Delete + </Button> + <Button variant="outline" size="large"> + Outline + </Button> + <Button variant="ghost" size="large"> + Ghost + </Button> + </div> + ); +} + +function renderDisabledButtons() { + return ( + <div className="flex flex-wrap gap-4"> + <Button variant="primary" disabled> + Primary Disabled + </Button> + <Button variant="secondary" disabled> + Secondary Disabled + </Button> + <Button variant="destructive" disabled> + Destructive Disabled + </Button> + <Button variant="outline" disabled> + Outline Disabled + </Button> + <Button variant="ghost" disabled> + Ghost Disabled + </Button> + </div> + ); +} + +function renderAllVariants() { + return ( + <div className="space-y-12 p-8"> + {/* Large buttons section */} + <div className="space-y-8"> + <h2 className="text-3xl font-semibold text-neutral-900"> + Large buttons + </h2> + <div className="flex flex-wrap gap-20"> + {/* Primary */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Primary + </div> + <div className="flex flex-col gap-8"> + <Button variant="primary" size="large"> + Save + </Button> + <Button variant="primary" size="large" loading> + Loading + </Button> + <Button variant="primary" size="large" disabled> + Disabled + </Button> + <Button + variant="primary" + size="large" + leftIcon={<Play className="h-5 w-5" />} + > + Play + </Button> + </div> + </div> + + {/* Secondary */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Secondary + </div> + <div className="flex flex-col gap-8"> + <Button variant="secondary" size="large"> + Save + </Button> + <Button variant="secondary" size="large" loading> + Loading + </Button> + <Button variant="secondary" size="large" disabled> + Disabled + </Button> + <Button + variant="secondary" + size="large" + leftIcon={<Play className="h-5 w-5" />} + > + Play + </Button> + </div> + </div> + + {/* Destructive */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Destructive + </div> + <div className="flex flex-col gap-8"> + <Button variant="destructive" size="large"> + Save + </Button> + <Button variant="destructive" size="large" loading> + Loading + </Button> + <Button variant="destructive" size="large" disabled> + Disabled + </Button> + <Button + variant="destructive" + size="large" + leftIcon={<Play className="h-5 w-5" />} + > + Play + </Button> + </div> + </div> + + {/* Outline */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Outline + </div> + <div className="flex flex-col gap-8"> + <Button variant="outline" size="large"> + Save + </Button> + <Button variant="outline" size="large" loading> + Loading + </Button> + <Button variant="outline" size="large" disabled> + Disabled + </Button> + <Button + variant="outline" + size="large" + leftIcon={<Play className="h-5 w-5" />} + > + Play + </Button> + </div> + </div> + + {/* Ghost */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Save + </div> + <div className="flex flex-col gap-8"> + <Button variant="ghost" size="large"> + Text + </Button> + <Button variant="ghost" size="large" loading> + Loading + </Button> + <Button variant="ghost" size="large" disabled> + Disabled + </Button> + <Button + variant="ghost" + size="large" + leftIcon={<Play className="h-5 w-5" />} + > + Play + </Button> + </div> + </div> + </div> + </div> + + {/* Small buttons section */} + <div className="space-y-8"> + <h2 className="text-3xl font-semibold text-neutral-900"> + Small buttons + </h2> + <div className="flex flex-wrap gap-20"> + {/* Primary Small */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Primary + </div> + <div className="flex flex-col gap-8"> + <Button variant="primary" size="small"> + Save + </Button> + <Button variant="primary" size="small" loading> + Loading + </Button> + <Button variant="primary" size="small" disabled> + Disabled + </Button> + <Button + variant="primary" + size="small" + leftIcon={<Play className="h-4 w-4" />} + > + Play + </Button> + </div> + </div> + + {/* Secondary Small */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Secondary + </div> + <div className="flex flex-col gap-8"> + <Button variant="secondary" size="small"> + Save + </Button> + <Button variant="secondary" size="small" disabled> + Disabled + </Button> + <Button + variant="secondary" + size="small" + leftIcon={<Play className="h-4 w-4" />} + > + Play + </Button> + </div> + </div> + + {/* Destructive Small */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Destructive + </div> + <div className="flex flex-col gap-8"> + <Button variant="destructive" size="small"> + Save + </Button> + <Button variant="destructive" size="small" disabled> + Disabled + </Button> + <Button + variant="destructive" + size="small" + leftIcon={<Play className="h-4 w-4" />} + > + Play + </Button> + </div> + </div> + + {/* Outline Small */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Outline + </div> + <div className="flex flex-col gap-8"> + <Button variant="outline" size="small"> + Save + </Button> + <Button variant="outline" size="small" disabled> + Disabled + </Button> + <Button + variant="outline" + size="small" + leftIcon={<Play className="h-4 w-4" />} + > + Play + </Button> + </div> + </div> + + {/* Ghost Small */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-neutral-900"> + Ghost + </div> + <div className="flex flex-col gap-8"> + <Button variant="ghost" size="small"> + Save + </Button> + <Button variant="ghost" size="small" disabled> + Disabled + </Button> + <Button + variant="ghost" + size="small" + leftIcon={<Play className="h-4 w-4" />} + > + Play + </Button> + </div> + </div> + </div> + </div> + + {/* Other button types */} + <div className="space-y-8"> + <h2 className="text-3xl font-semibold text-neutral-900"> + Other button types + </h2> + <div className="flex gap-20"> + {/* Link */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-zinc-800"> + Link + </div> + <div className="flex flex-col gap-8"> + <Button variant="link">Add to library</Button> + </div> + </div> + + {/* Icon */} + <div className="flex flex-col gap-5"> + <div className="font-['Geist'] text-base font-medium text-zinc-800"> + Icon + </div> + <div className="flex flex-col gap-8"> + <Button variant="icon" size="icon"> + <Plus className="h-4 w-4" /> + </Button> + <Button variant="primary" size="icon" className="bg-zinc-700"> + <Plus className="h-4 w-4" /> + </Button> + <Button variant="icon" size="icon" disabled> + <Plus className="h-4 w-4" /> + </Button> + </div> + </div> + </div> + </div> + </div> + ); +} diff --git a/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx b/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx new file mode 100644 index 0000000000..556b41c052 --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Button/Button.tsx @@ -0,0 +1,81 @@ +import { cn } from "@/lib/utils"; +import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr"; +import { cva, type VariantProps } from "class-variance-authority"; +import React from "react"; + +// Extended button variants based on our design system +const extendedButtonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 font-['Geist'] leading-snug border", + { + variants: { + variant: { + primary: + "bg-zinc-800 border-zinc-800 text-white hover:bg-zinc-900 hover:border-zinc-900 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1", + secondary: + "bg-zinc-100 border-zinc-100 text-black hover:bg-zinc-300 hover:border-zinc-300 rounded-full disabled:text-zinc-300 disabled:bg-zinc-50 disabled:border-zinc-50 disabled:opacity-1", + destructive: + "bg-red-500 border-red-500 text-white hover:bg-red-600 hover:border-red-600 rounded-full disabled:text-white disabled:bg-zinc-200 disabled:border-zinc-200 disabled:opacity-1", + outline: + "bg-transparent border-zinc-700 text-black hover:bg-zinc-100 hover:border-zinc-700 rounded-full disabled:border-zinc-200 disabled:text-zinc-200 disabled:opacity-1", + ghost: + "bg-transparent border-transparent text-black hover:bg-zinc-50 hover:border-zinc-50 rounded-full disabled:text-zinc-200 disabled:opacity-1", + link: "bg-transparent border-transparent text-black hover:underline rounded-none p-0 h-auto min-w-auto disabled:opacity-1", + icon: "bg-white text-black border border-zinc-600 hover:bg-zinc-100 rounded-[96px] disabled:opacity-1", + }, + size: { + small: "px-3 py-2 text-sm gap-1.5 h-[2.25rem]", + large: "min-w-20 px-4 py-3 text-sm gap-2", + icon: "p-3", + }, + }, + defaultVariants: { + variant: "primary", + size: "large", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes<HTMLButtonElement>, + VariantProps<typeof extendedButtonVariants> { + loading?: boolean; + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + asChild?: boolean; +} + +function Button({ + className, + variant, + size, + loading = false, + leftIcon, + rightIcon, + children, + disabled, + ...props +}: ButtonProps) { + const isDisabled = disabled; + + return ( + <button + className={cn( + extendedButtonVariants({ variant, size, className }), + loading && "pointer-events-none", + )} + disabled={isDisabled} + {...props} + > + {loading && ( + <CircleNotchIcon className="h-4 w-4 animate-spin" weight="bold" /> + )} + {!loading && leftIcon} + {children} + {!loading && rightIcon} + </button> + ); +} + +Button.displayName = "Button"; + +export { Button, extendedButtonVariants }; diff --git a/autogpt_platform/frontend/src/components/atoms/Input/Input.stories.tsx b/autogpt_platform/frontend/src/components/atoms/Input/Input.stories.tsx new file mode 100644 index 0000000000..ccde5622bf --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Input/Input.stories.tsx @@ -0,0 +1,154 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { Input } from "./Input"; + +const meta: Meta<typeof Input> = { + title: "Atoms/Input", + tags: ["autodocs"], + component: Input, + parameters: { + layout: "centered", + docs: { + description: { + component: + "Input component based on our design system. Built on top of shadcn/ui input with custom styling matching Figma designs.", + }, + }, + }, + argTypes: { + type: { + control: "select", + options: ["text", "email", "password", "number", "amount", "tel", "url"], + description: "Input type", + }, + placeholder: { + control: "text", + description: "Placeholder text", + }, + value: { + control: "text", + description: "The value of the input", + }, + label: { + control: "text", + description: + "Label text (used as placeholder if no placeholder provided)", + }, + disabled: { + control: "boolean", + description: "Disable the input", + }, + hideLabel: { + control: "boolean", + description: "Hide the label", + }, + decimalCount: { + control: "number", + description: + "Number of decimal places allowed (only for amount type). Default is 4.", + }, + error: { + control: "text", + description: "Error message to display below the input", + }, + }, + args: { + placeholder: "Enter text...", + type: "text", + value: "", + disabled: false, + hideLabel: false, + decimalCount: 4, + }, +}; + +export default meta; +type Story = StoryObj<typeof meta>; + +// Basic variants +export const Default: Story = { + args: { + placeholder: "Enter your text", + label: "Full Name", + }, +}; + +export const WithoutLabel: Story = { + args: { + label: "Full Name", + hideLabel: true, + }, +}; + +export const Disabled: Story = { + args: { + placeholder: "This field is disabled", + label: "Full Name", + disabled: true, + }, +}; + +export const WithError: Story = { + args: { + label: "Email", + type: "email", + placeholder: "Enter your email", + error: "Please enter a valid email address", + }, +}; + +export const InputTypes: Story = { + render: renderInputTypes, + parameters: { + controls: { + disable: true, + }, + docs: { + description: { + story: + "Complete showcase of all input types with their specific behaviors. Test each input type to verify filtering and formatting works correctly.", + }, + }, + }, +}; + +// Render functions as function declarations +function renderInputTypes() { + return ( + <div className="w-full max-w-md space-y-8"> + <Input label="Full Name" type="text" placeholder="Enter your full name" /> + <Input label="Email" type="email" placeholder="your.email@example.com" /> + <Input + label="Password" + type="password" + placeholder="Enter your password" + /> + <div className="flex flex-col gap-4"> + <p className="font-mono text-sm"> + If type="number" prop is provided, the input will allow only + positive or negative numbers. No decimal limiting. + </p> + <Input label="Age" type="number" placeholder="Enter your age" /> + </div> + <div className="flex flex-col gap-4"> + <p className="font-mono text-sm"> + If type="amount" prop is provided, it formats numbers with + commas (1000 → 1,000) and limits decimals via decimalCount prop. + </p> + <Input + label="Price" + type="amount" + placeholder="Enter amount" + decimalCount={2} + /> + </div> + <div className="flex flex-col gap-4"> + <p className="font-mono text-sm"> + If type="tel" prop is provided, the input will allow only + numbers, spaces, parentheses (), plus +, and brackets []. + </p> + <Input label="Phone" type="tel" placeholder="+1 (555) 123-4567" /> + </div> + <Input label="Website" type="url" placeholder="https://example.com" /> + </div> + ); +} diff --git a/autogpt_platform/frontend/src/components/atoms/Input/Input.tsx b/autogpt_platform/frontend/src/components/atoms/Input/Input.tsx new file mode 100644 index 0000000000..561d8896f3 --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Input/Input.tsx @@ -0,0 +1,67 @@ +import { Input as BaseInput, type InputProps } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; +import { Text } from "../Text/Text"; +import { useInput } from "./useInput"; + +export interface TextFieldProps extends InputProps { + label: string; + hideLabel?: boolean; + decimalCount?: number; // Only used for type="amount" + error?: string; +} + +export function Input({ + className, + label, + placeholder, + hideLabel = false, + decimalCount, + error, + ...props +}: TextFieldProps) { + const { handleInputChange } = useInput({ ...props, decimalCount }); + + const input = ( + <BaseInput + className={cn( + // Override the default input styles with Figma design + "h-[2.875rem] rounded-3xl border border-zinc-200 bg-white px-4 py-2.5 shadow-none", + "font-normal leading-6 text-black", + "placeholder:font-normal placeholder:text-zinc-400", + // Focus and hover states + "focus:border-zinc-400 focus:shadow-none focus:outline-none focus:ring-1 focus:ring-zinc-400 focus:ring-offset-0", + // Error state + error && + "border-2 border-red-500 focus:border-red-500 focus:ring-red-500", + className, + )} + type={props.type} + placeholder={placeholder || label} + onChange={handleInputChange} + {...(hideLabel ? { "aria-label": label } : {})} + {...props} + /> + ); + + const inputWithError = ( + <div className="flex flex-col gap-1"> + {input} + {error && ( + <Text variant="small-medium" as="span" className="!text-red-500"> + {error} + </Text> + )} + </div> + ); + + return hideLabel ? ( + inputWithError + ) : ( + <label className="flex flex-col gap-2"> + <Text variant="body-medium" as="span" className="text-black"> + {label} + </Text> + {inputWithError} + </label> + ); +} diff --git a/autogpt_platform/frontend/src/components/atoms/Input/helpers.ts b/autogpt_platform/frontend/src/components/atoms/Input/helpers.ts new file mode 100644 index 0000000000..715c834c56 --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Input/helpers.ts @@ -0,0 +1,65 @@ +export const NUMBER_REGEX = /[^0-9.-]/g; +export const PHONE_REGEX = /[^0-9\s()\+\[\]]/g; + +export function formatAmountWithCommas(value: string): string { + if (!value) return value; + + const parts = value.split("."); + const integerPart = parts[0]; + const decimalPart = parts[1]; + + // Add commas to integer part + const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ","); + + // Check if there was a decimal point in the original value + if (value.includes(".")) { + return decimalPart + ? `${formattedInteger}.${decimalPart}` + : `${formattedInteger}.`; + } + + return formattedInteger; +} + +export function filterNumberInput(value: string): string { + let filteredValue = value; + + // Remove all non-numeric characters except . and - + filteredValue = value.replace(NUMBER_REGEX, ""); + + // Handle multiple decimal points - keep only the first one + const parts = filteredValue.split("."); + if (parts.length > 2) { + filteredValue = parts[0] + "." + parts.slice(1).join(""); + } + + // Handle minus signs - only allow at the beginning + if (filteredValue.indexOf("-") > 0) { + const hadMinusAtStart = value.startsWith("-"); + filteredValue = filteredValue.replace(/-/g, ""); + if (hadMinusAtStart) { + filteredValue = "-" + filteredValue; + } + } + + return filteredValue; +} + +export function limitDecimalPlaces( + value: string, + decimalCount: number, +): string { + const [integerPart, decimalPart] = value.split("."); + if (decimalPart && decimalPart.length > decimalCount) { + return `${integerPart}.${decimalPart.substring(0, decimalCount)}`; + } + return value; +} + +export function filterPhoneInput(value: string): string { + return value.replace(PHONE_REGEX, ""); +} + +export function removeCommas(value: string): string { + return value.replace(/,/g, ""); +} diff --git a/autogpt_platform/frontend/src/components/atoms/Input/useInput.ts b/autogpt_platform/frontend/src/components/atoms/Input/useInput.ts new file mode 100644 index 0000000000..b931bc663e --- /dev/null +++ b/autogpt_platform/frontend/src/components/atoms/Input/useInput.ts @@ -0,0 +1,58 @@ +import { InputProps } from "@/components/ui/input"; +import { + filterNumberInput, + filterPhoneInput, + formatAmountWithCommas, + limitDecimalPlaces, + removeCommas, +} from "./helpers"; + +interface ExtendedInputProps extends InputProps { + decimalCount?: number; +} + +export function useInput(args: ExtendedInputProps) { + function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) { + const { value } = e.target; + const decimalCount = args.decimalCount ?? 4; + + let processedValue = value; + + if (args.type === "number") { + // Basic number filtering - no decimal limiting + const filteredValue = filterNumberInput(value); + processedValue = filteredValue; + } else if (args.type === "amount") { + // Amount type with decimal limiting and comma formatting + const cleanValue = removeCommas(value); + let filteredValue = filterNumberInput(cleanValue); + filteredValue = limitDecimalPlaces(filteredValue, decimalCount); + + const displayValue = formatAmountWithCommas(filteredValue); + e.target.value = displayValue; + processedValue = filteredValue; // Pass clean value to parent + } else if (args.type === "tel") { + processedValue = filterPhoneInput(value); + } + + // Call onChange with processed value + if (args.onChange) { + // Only create synthetic event if we need to change the value + if (processedValue !== value || args.type === "amount") { + const syntheticEvent = { + ...e, + target: { + ...e.target, + value: processedValue, + }, + } as React.ChangeEvent<HTMLInputElement>; + + args.onChange(syntheticEvent); + } else { + args.onChange(e); + } + } + } + + return { handleInputChange }; +} diff --git a/autogpt_platform/frontend/src/components/_new/Text/Text.stories.tsx b/autogpt_platform/frontend/src/components/atoms/Text/Text.stories.tsx similarity index 96% rename from autogpt_platform/frontend/src/components/_new/Text/Text.stories.tsx rename to autogpt_platform/frontend/src/components/atoms/Text/Text.stories.tsx index 5fffcccfb4..4456539b5c 100644 --- a/autogpt_platform/frontend/src/components/_new/Text/Text.stories.tsx +++ b/autogpt_platform/frontend/src/components/atoms/Text/Text.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Text, textVariants, type TextVariant } from "./Text"; -import { StoryCode } from "@/stories/helpers/StoryCode"; +import { StoryCode } from "@/components/tokens/helpers/StoryCode"; +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { Text, textVariants } from "./Text"; const meta: Meta<typeof Text> = { - title: "Design System/Atoms/Text", + title: "Atoms/Text", component: Text, tags: ["autodocs"], parameters: { diff --git a/autogpt_platform/frontend/src/components/_new/Text/Text.tsx b/autogpt_platform/frontend/src/components/atoms/Text/Text.tsx similarity index 100% rename from autogpt_platform/frontend/src/components/_new/Text/Text.tsx rename to autogpt_platform/frontend/src/components/atoms/Text/Text.tsx diff --git a/autogpt_platform/frontend/src/components/_new/Text/helpers.ts b/autogpt_platform/frontend/src/components/atoms/Text/helpers.ts similarity index 100% rename from autogpt_platform/frontend/src/components/_new/Text/helpers.ts rename to autogpt_platform/frontend/src/components/atoms/Text/helpers.ts diff --git a/autogpt_platform/frontend/src/components/auth/AuthFeedback.tsx b/autogpt_platform/frontend/src/components/auth/AuthFeedback.tsx index 3b518b4e09..6867cd51d3 100644 --- a/autogpt_platform/frontend/src/components/auth/AuthFeedback.tsx +++ b/autogpt_platform/frontend/src/components/auth/AuthFeedback.tsx @@ -1,5 +1,5 @@ import { AlertCircle, CheckCircle } from "lucide-react"; -import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { HelpItem } from "@/components/auth/help-item"; import { BehaveAs } from "@/lib/utils"; diff --git a/autogpt_platform/frontend/src/components/auth/GoogleOAuthButton.tsx b/autogpt_platform/frontend/src/components/auth/GoogleOAuthButton.tsx index a74b603f4d..1f9b0a43d5 100644 --- a/autogpt_platform/frontend/src/components/auth/GoogleOAuthButton.tsx +++ b/autogpt_platform/frontend/src/components/auth/GoogleOAuthButton.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import { FaGoogle, FaSpinner } from "react-icons/fa"; import { Button } from "../ui/button"; diff --git a/autogpt_platform/frontend/src/components/auth/Turnstile.tsx b/autogpt_platform/frontend/src/components/auth/Turnstile.tsx index a0e2ccc872..ee3dd9e17f 100644 --- a/autogpt_platform/frontend/src/components/auth/Turnstile.tsx +++ b/autogpt_platform/frontend/src/components/auth/Turnstile.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; +import { useEffect, useRef, useState } from "react"; export interface TurnstileProps { siteKey: string; @@ -120,7 +120,7 @@ export function Turnstile({ ]); // Method to reset the widget manually - const reset = useCallback(() => { + useEffect(() => { if (loaded && widgetIdRef.current && window.turnstile && shouldRender) { window.turnstile.reset(widgetIdRef.current); } diff --git a/autogpt_platform/frontend/src/components/cronScheduler.tsx b/autogpt_platform/frontend/src/components/cronScheduler.tsx index 9625b2dd19..37022fb171 100644 --- a/autogpt_platform/frontend/src/components/cronScheduler.tsx +++ b/autogpt_platform/frontend/src/components/cronScheduler.tsx @@ -9,12 +9,7 @@ import { import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Separator } from "./ui/separator"; import { CronExpressionManager } from "@/lib/monitor/cronExpressionManager"; @@ -31,9 +26,9 @@ export function CronScheduler({ }: CronSchedulerProps) { const [frequency, setFrequency] = useState< "minute" | "hour" | "daily" | "weekly" | "monthly" | "yearly" | "custom" - >("minute"); + >("daily"); const [selectedDays, setSelectedDays] = useState<number[]>([]); - const [selectedTime, setSelectedTime] = useState<string>("00:00"); + const [selectedTime, setSelectedTime] = useState<string>("09:00"); const [showCustomDays, setShowCustomDays] = useState<boolean>(false); const [selectedMinute, setSelectedMinute] = useState<string>("0"); const [customInterval, setCustomInterval] = useState<{ @@ -82,7 +77,7 @@ export function CronScheduler({ <Select onValueChange={(value: any) => setFrequency(value)} - defaultValue="minute" + defaultValue="daily" > <SelectTrigger> <SelectValue placeholder="Select frequency" /> diff --git a/autogpt_platform/frontend/src/components/library/library-action-header.tsx b/autogpt_platform/frontend/src/components/library/library-action-header.tsx index 647dac40b3..b5d7f2c9c3 100644 --- a/autogpt_platform/frontend/src/components/library/library-action-header.tsx +++ b/autogpt_platform/frontend/src/components/library/library-action-header.tsx @@ -2,7 +2,7 @@ import LibraryUploadAgentDialog from "./library-upload-agent-dialog"; import LibrarySearchBar from "./library-search-bar"; -interface LibraryActionHeaderProps {} +type LibraryActionHeaderProps = Record<string, never>; /** * LibraryActionHeader component - Renders a header with search, notifications and filters diff --git a/autogpt_platform/frontend/src/components/library/library-notification-card.tsx b/autogpt_platform/frontend/src/components/library/library-notification-card.tsx index 3adc1dd6a7..03df815f31 100644 --- a/autogpt_platform/frontend/src/components/library/library-notification-card.tsx +++ b/autogpt_platform/frontend/src/components/library/library-notification-card.tsx @@ -24,7 +24,7 @@ interface NotificationCardProps { } const NotificationCard = ({ - notification: { id, type, title, content, mediaUrl }, + notification: { type, title, content, mediaUrl }, onClose, }: NotificationCardProps) => { const barHeights = Array.from({ length: 60 }, () => diff --git a/autogpt_platform/frontend/src/components/library/library-sort-menu.tsx b/autogpt_platform/frontend/src/components/library/library-sort-menu.tsx index 3e47aa114d..c5ac2e819c 100644 --- a/autogpt_platform/frontend/src/components/library/library-sort-menu.tsx +++ b/autogpt_platform/frontend/src/components/library/library-sort-menu.tsx @@ -19,7 +19,7 @@ export default function LibrarySortMenu(): React.ReactNode { setLibrarySort(value); setAgentLoading(true); await new Promise((resolve) => setTimeout(resolve, 1000)); - let response = await api.listLibraryAgents({ + const response = await api.listLibraryAgents({ search_term: searchTerm, sort_by: value, page: 1, diff --git a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx index d06ebe3d1c..05cc827dbb 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx @@ -35,7 +35,6 @@ import { DialogDescription, DialogFooter, } from "@/components/ui/dialog"; -import { useToast } from "@/components/ui/use-toast"; import RunnerInputUI from "@/components/runner-ui/RunnerInputUI"; import useAgentGraph from "@/hooks/useAgentGraph"; import { useBackendAPI } from "@/lib/autogpt-server-api/context"; @@ -52,7 +51,6 @@ export const FlowInfo: React.FC< useAgentGraph(flow.graph_id, flow.graph_version, undefined, false); const api = useBackendAPI(); - const { toast } = useToast(); const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null); const [selectedVersion, setSelectedFlowVersion] = useState( @@ -170,7 +168,7 @@ export const FlowInfo: React.FC< <DropdownMenuSeparator /> <DropdownMenuRadioGroup value={String(selectedVersion)} - onValueChange={(choice) => + onValueChange={(choice: string) => setSelectedFlowVersion( choice == "all" ? choice : Number(choice), ) diff --git a/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx b/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx index 5918552f6a..92310031aa 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx @@ -58,7 +58,7 @@ export const FlowRunsTimeline = ({ tickFormatter={(s) => (s > 90 ? `${Math.round(s / 60)}m` : `${s}s`)} /> <Tooltip - content={({ payload, label }) => { + content={({ payload }) => { if (payload && payload.length) { const data: GraphExecutionMeta & { time: number; diff --git a/autogpt_platform/frontend/src/components/node-input-components.tsx b/autogpt_platform/frontend/src/components/node-input-components.tsx index 24e9863c42..b1dabfb99c 100644 --- a/autogpt_platform/frontend/src/components/node-input-components.tsx +++ b/autogpt_platform/frontend/src/components/node-input-components.tsx @@ -77,7 +77,6 @@ const NodeObjectInputTree: FC<NodeObjectInputTreeProps> = ({ handleInputChange, errors, className, - displayName, }) => { object ||= ("default" in schema ? schema.default : null) ?? {}; return ( @@ -126,12 +125,10 @@ const NodeDateTimeInput: FC<{ hideTime?: boolean; }> = ({ selfKey, - schema, value = "", error, handleInputChange, className, - displayName, hideDate = false, hideTime = false, }) => { @@ -219,7 +216,6 @@ const NodeFileInput: FC<{ displayName: string; }> = ({ selfKey, - schema, value = "", error, handleInputChange, @@ -279,7 +275,7 @@ const NodeFileInput: FC<{ variant="ghost" className="text-red-500 hover:text-red-700" onClick={() => { - inputRef.current && (inputRef.current!.value = ""); + if (inputRef.current) inputRef.current.value = ""; handleInputChange(selfKey, ""); }} > @@ -884,13 +880,6 @@ const NodeKeyValueInput: FC<{ ); }; -// Checking if schema is type of string -function isStringSubSchema( - schema: BlockIOSimpleTypeSubSchema, -): schema is BlockIOStringSubSchema { - return "type" in schema && schema.type === "string"; -} - const NodeArrayInput: FC<{ nodeId: string; selfKey: string; @@ -1229,15 +1218,7 @@ const NodeBooleanInput: FC<{ handleInputChange: NodeObjectInputTreeProps["handleInputChange"]; className?: string; displayName: string; -}> = ({ - selfKey, - schema, - value, - error, - handleInputChange, - className, - displayName, -}) => { +}> = ({ selfKey, schema, value, error, handleInputChange, className }) => { value ||= schema.default ?? false; return ( <div className={className}> diff --git a/autogpt_platform/frontend/src/components/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/components/onboarding/onboarding-provider.tsx index 72589bc8d0..0f49bbdc75 100644 --- a/autogpt_platform/frontend/src/components/onboarding/onboarding-provider.tsx +++ b/autogpt_platform/frontend/src/components/onboarding/onboarding-provider.tsx @@ -21,7 +21,6 @@ import { } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import Link from "next/link"; -import { set } from "lodash"; const OnboardingContext = createContext< | { diff --git a/autogpt_platform/frontend/src/stories/Overview.stories.tsx b/autogpt_platform/frontend/src/components/overview.stories.tsx similarity index 99% rename from autogpt_platform/frontend/src/stories/Overview.stories.tsx rename to autogpt_platform/frontend/src/components/overview.stories.tsx index d6c70ce41d..b842b043dc 100644 --- a/autogpt_platform/frontend/src/stories/Overview.stories.tsx +++ b/autogpt_platform/frontend/src/components/overview.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from "@storybook/react"; +import type { Meta, StoryObj } from "@storybook/nextjs"; import Image from "next/image"; function RightArrow() { @@ -456,7 +456,6 @@ const meta: Meta<typeof OverviewComponent> = { component: OverviewComponent, parameters: { layout: "fullscreen", - controls: { disable: true }, }, }; diff --git a/autogpt_platform/frontend/src/components/profile/settings/SettingsForm.tsx b/autogpt_platform/frontend/src/components/profile/settings/SettingsForm.tsx index fbd1202f5a..8b8ca6449a 100644 --- a/autogpt_platform/frontend/src/components/profile/settings/SettingsForm.tsx +++ b/autogpt_platform/frontend/src/components/profile/settings/SettingsForm.tsx @@ -20,10 +20,7 @@ import { Switch } from "@/components/ui/switch"; import { Separator } from "@/components/ui/separator"; import { updateSettings } from "@/app/(platform)/profile/(user)/settings/actions"; import { toast } from "@/components/ui/use-toast"; -import { - NotificationPreference, - NotificationPreferenceDTO, -} from "@/lib/autogpt-server-api"; +import { NotificationPreferenceDTO } from "@/lib/autogpt-server-api"; const formSchema = z .object({ diff --git a/autogpt_platform/frontend/src/components/runner-ui/RunnerOutputUI.tsx b/autogpt_platform/frontend/src/components/runner-ui/RunnerOutputUI.tsx index e62f9e499d..755df754c2 100644 --- a/autogpt_platform/frontend/src/components/runner-ui/RunnerOutputUI.tsx +++ b/autogpt_platform/frontend/src/components/runner-ui/RunnerOutputUI.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React from "react"; import { Sheet, SheetContent, diff --git a/autogpt_platform/frontend/src/components/styles/colors.ts b/autogpt_platform/frontend/src/components/styles/colors.ts new file mode 100644 index 0000000000..1a6616e3fd --- /dev/null +++ b/autogpt_platform/frontend/src/components/styles/colors.ts @@ -0,0 +1,79 @@ +export const colors = { + slate: { + 50: "#f0f1f3", + 100: "#cfd4db", + 200: "#b8bfca", + 300: "#97a2b1", + 400: "#8390a2", + 500: "#64748b", + 600: "#5b6a7e", + 700: "#475263", + 800: "#37404c", + 900: "#2a313a", + }, + zinc: { + 50: "#f4f4f5", + 100: "#e5e5e6", + 200: "#cacace", + 300: "#adadb3", + 400: "#8d8d95", + 500: "#71717a", + 600: "#65656c", + 700: "#505057", + 800: "#3e3e43", + 900: "#2C2C30", + }, + red: { + 50: "#fdecec", + 100: "#fac5c5", + 200: "#f8a9a9", + 300: "#f48282", + 400: "#f26969", + 500: "#ef4444", + 600: "#d93e3e", + 700: "#aa3030", + 800: "#832525", + 900: "#641d1d", + }, + orange: { + 50: "#fff3e6", + 100: "#ffdab0", + 200: "#ffc88a", + 300: "#feaf54", + 400: "#fe9f33", + 500: "#fe8700", + 600: "#e77b00", + 700: "#b46000", + 800: "#8c4a00", + 900: "#6b3900", + }, + yellow: { + 50: "#fef9e6", + 100: "#fcebb0", + 200: "#fae28a", + 300: "#f8d554", + 400: "#f7cd33", + 500: "#f5c000", + 600: "#dfaf00", + 700: "#ae8800", + 800: "#876a00", + 900: "#675100", + }, + green: { + 50: "#e8f6ed", + 100: "#b7e2c7", + 200: "#94d5ac", + 300: "#63c186", + 400: "#45b56e", + 500: "#16a34a", + 600: "#149443", + 700: "#107435", + 800: "#0c5a29", + 900: "#09441f", + }, + + // Special semantic colors + white: "#fefefe", + black: "#141414", + lightGrey: "#F3F4F6", +}; diff --git a/autogpt_platform/frontend/src/stories/border-radius.stories.tsx b/autogpt_platform/frontend/src/components/tokens/border-radius.stories.tsx similarity index 97% rename from autogpt_platform/frontend/src/stories/border-radius.stories.tsx rename to autogpt_platform/frontend/src/components/tokens/border-radius.stories.tsx index 8b9620c896..213d09f246 100644 --- a/autogpt_platform/frontend/src/stories/border-radius.stories.tsx +++ b/autogpt_platform/frontend/src/components/tokens/border-radius.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Text } from "@/components/_new/Text/Text"; -import { StoryCode } from "@/stories/helpers/StoryCode"; +import { Text } from "@/components/atoms/Text/Text"; +import type { Meta } from "@storybook/nextjs"; import { SquareArrowOutUpRight } from "lucide-react"; +import { StoryCode } from "./helpers/StoryCode"; const meta: Meta = { - title: "Design System/ Tokens /Border Radius", + title: "Tokens /Border Radius", parameters: { layout: "fullscreen", controls: { disable: true }, @@ -248,5 +248,3 @@ export function AllVariants() { </div> ); } - -type Story = StoryObj<typeof meta>; diff --git a/autogpt_platform/frontend/src/components/tokens/colors.stories.tsx b/autogpt_platform/frontend/src/components/tokens/colors.stories.tsx new file mode 100644 index 0000000000..3b60d23b16 --- /dev/null +++ b/autogpt_platform/frontend/src/components/tokens/colors.stories.tsx @@ -0,0 +1,323 @@ +import { Text } from "@/components/atoms/Text/Text"; +import { colors } from "@/components/styles/colors"; +import type { Meta } from "@storybook/nextjs"; +import { StoryCode } from "./helpers/StoryCode"; + +const meta: Meta = { + title: "Tokens /Colors", + parameters: { + layout: "fullscreen", + controls: { disable: true }, + }, +}; + +export default meta; + +// Helper function to convert hex to RGB +function hexToRgb(hex: string): string { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (!result) return hex; + + const r = parseInt(result[1], 16); + const g = parseInt(result[2], 16); + const b = parseInt(result[3], 16); + + return `rgb(${r}, ${g}, ${b})`; +} + +// Generate color categories from colors.ts +const colorCategories = Object.entries(colors) + .filter(([key]) => !["white", "black"].includes(key)) + .map(([colorName, colorShades]) => { + const descriptions: Record<string, string> = { + slate: "Cool gray tones for modern, professional interfaces", + zinc: "Neutral gray scale for backgrounds and subtle elements", + red: "Error states, warnings, and destructive actions", + orange: "Warnings, notifications, and secondary call-to-actions", + yellow: "Highlights, cautions, and attention-grabbing elements", + green: "Success states, confirmations, and positive actions", + }; + + return { + name: colorName.charAt(0).toUpperCase() + colorName.slice(1), + description: descriptions[colorName] || `${colorName} color variations`, + colors: Object.entries(colorShades as Record<string, string>).map( + ([shade, hex]) => ({ + name: `${colorName}-${shade}`, + hex, + rgb: hexToRgb(hex), + class: `bg-${colorName}-${shade}`, + textClass: `text-${colorName}-${shade}`, + }), + ), + }; + }); + +// Special colors from colors.ts +const specialColors = [ + { + name: "Text", + description: "Primary text colors for content and typography", + colors: [ + { + name: "text-white", + hex: colors.white, + rgb: hexToRgb(colors.white), + class: "text-white", + bgClass: "bg-white", + }, + { + name: "text-black", + hex: colors.black, + rgb: hexToRgb(colors.black), + class: "text-black", + bgClass: "bg-black", + }, + ], + }, + { + name: "Background", + description: "Standard background colors for layouts and surfaces", + colors: [ + { + name: "bg-white", + hex: colors.white, + rgb: hexToRgb(colors.white), + class: "bg-white", + textClass: "text-white", + }, + { + name: "bg-light-grey", + hex: colors.lightGrey, + rgb: hexToRgb(colors.lightGrey), + class: "bg-light-grey", + textClass: "text-light-grey", + }, + ], + }, +]; + +export function AllVariants() { + return ( + <div className="space-y-12"> + {/* Color System Documentation */} + <div className="space-y-8"> + <div> + <Text variant="h1" className="mb-4 text-zinc-800"> + Color Palette + </Text> + <Text variant="large" className="text-zinc-600"> + Use only these approved colors in your components. Many are named + like Tailwind's default theme but override those values with + our custom palette. + </Text> + </div> + + <div className="grid gap-8 md:grid-cols-2"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + How to Use + </Text> + <div className="space-y-4"> + <div className="rounded-lg border border-gray-200 p-4"> + <Text variant="body" className="mb-2 text-zinc-600"> + Use any of the approved colors combined with{" "} + <a + href="https://tailwindcss.com/docs/colors" + target="_blank" + rel="noopener noreferrer" + className="text-blue-500" + > + tailwind classes + </a> + </Text> + <div className="font-mono text-sm text-zinc-800"> + bg-slate-500 → background-color: {colors.slate[500]} + </div> + </div> + + <div className="rounded-lg border-2 border-dashed border-red-200 bg-red-50 p-4"> + <Text + variant="body-medium" + className="mb-2 font-semibold text-red-800" + > + ⚠️ Only Use These Colors + </Text> + <Text variant="body" className="text-red-700"> + These are the ONLY approved colors. Don't use other + Tailwind colors or arbitrary values. + </Text> + </div> + </div> + </div> + + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Color Selection Guide + </Text> + <div className="space-y-2 text-zinc-600"> + <Text variant="body"> + • <strong>50-200:</strong> Light backgrounds, subtle borders + </Text> + <Text variant="body"> + • <strong>300-500:</strong> Interactive elements, primary colors + </Text> + <Text variant="body"> + • <strong>600-900:</strong> Text colors, dark backgrounds + </Text> + <Text variant="body"> + • <strong>Semantic:</strong> Red for errors, green for success + </Text> + </div> + </div> + </div> + </div> + + {/* Special Colors */} + <div className="space-y-8"> + <div className="grid gap-8 md:grid-cols-2"> + {specialColors.map((category) => ( + <div key={category.name} className="space-y-4"> + <div> + <Text + variant="h3" + className="mb-1 text-lg font-semibold text-zinc-800" + > + {category.name} + </Text> + <Text variant="body" className="text-zinc-600"> + {category.description} + </Text> + </div> + <div className="grid gap-3 sm:grid-cols-1"> + {category.colors.map((color) => ( + <div + key={color.name} + className="flex items-center gap-4 rounded-lg border border-gray-200 p-4" + > + <div + className="h-12 w-12 flex-shrink-0 rounded border border-gray-300" + style={{ backgroundColor: color.hex }} + ></div> + <div className="flex-1 space-y-1"> + <Text + variant="body-medium" + className="font-mono text-zinc-800" + > + {color.name} + </Text> + <Text variant="small" className="font-mono text-zinc-500"> + {color.class} + </Text> + <div className="space-y-0.5"> + <p className="font-mono text-xs text-zinc-500"> + {color.hex} + </p> + <p className="font-mono text-xs text-zinc-500"> + {color.rgb} + </p> + </div> + </div> + </div> + ))} + </div> + </div> + ))} + </div> + </div> + + {/* Color Categories */} + <div className="space-y-12"> + {colorCategories.map((category) => ( + <div key={category.name} className="space-y-4"> + <div> + <Text + variant="h3" + className="mb-1 text-lg font-semibold text-zinc-800" + > + {category.name} + </Text> + <Text variant="body" className="text-zinc-600"> + {category.description} + </Text> + </div> + <div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5"> + {category.colors.map((color) => ( + <div + key={color.name} + className="space-y-3 rounded-lg border border-gray-200 p-4" + > + <div + className="h-16 w-full rounded border border-gray-300" + style={{ backgroundColor: color.hex }} + ></div> + <div className="space-y-1"> + <Text + variant="body-medium" + className="font-mono text-zinc-800" + > + {color.name} + </Text> + <Text variant="small" className="font-mono text-zinc-500"> + {color.class} + </Text> + <div className="space-y-0.5"> + <p className="font-mono text-xs text-zinc-500"> + {color.hex} + </p> + <p className="font-mono text-xs text-zinc-500"> + {color.rgb} + </p> + </div> + </div> + </div> + ))} + </div> + </div> + ))} + </div> + + {/* Usage Examples */} + <div className="space-y-8"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Usage Examples + </Text> + </div> + + <StoryCode + code={`// ✅ CORRECT - Use approved design tokens +<div className="bg-slate-100 text-slate-800"> + Content with approved colors +</div> + +<button className="bg-green-500 text-white hover:bg-green-600"> + Success Button +</button> + +// Text colors +<h1 className="text-black">Primary heading</h1> +<p className="text-zinc-600">Secondary text</p> + +// Semantic usage +<div className="bg-green-50 border-green-200 text-green-800">Success</div> +<div className="bg-red-50 border-red-200 text-red-800">Error</div> +<div className="bg-yellow-50 border-yellow-200 text-yellow-800">Warning</div> + +// ❌ INCORRECT - Don't use these +<div className="bg-blue-500 text-purple-600">❌ Not approved</div> +<div className="bg-[#1234ff]">❌ Arbitrary values</div>`} + /> + </div> + </div> + ); +} diff --git a/autogpt_platform/frontend/src/stories/helpers/StoryCode.tsx b/autogpt_platform/frontend/src/components/tokens/helpers/StoryCode.tsx similarity index 100% rename from autogpt_platform/frontend/src/stories/helpers/StoryCode.tsx rename to autogpt_platform/frontend/src/components/tokens/helpers/StoryCode.tsx diff --git a/autogpt_platform/frontend/src/components/tokens/icons.stories.tsx b/autogpt_platform/frontend/src/components/tokens/icons.stories.tsx new file mode 100644 index 0000000000..a6b53bc21d --- /dev/null +++ b/autogpt_platform/frontend/src/components/tokens/icons.stories.tsx @@ -0,0 +1,443 @@ +import { Text } from "@/components/atoms/Text/Text"; +import { + Alien, + ArrowClockwise, + ArrowCounterClockwise, + ArrowLeft, + ArrowRight, + Bell, + Books, + Check, + CloudArrowUp, + Copy, + Cube, + Download, + FacebookLogo, + FloppyDisk, + FlowArrow, + Gear, + GithubLogo, + Info, + InstagramLogo, + Key, + LinkedinLogo, + List, + Package, + PencilSimple, + Play, + ArrowClockwise as Redo, + SignOut, + SquaresFour, + Trash, + User, + UserCircle, + UserPlus, + Warning, + X, + XLogo, + YoutubeLogo, +} from "@phosphor-icons/react"; +import type { Meta } from "@storybook/nextjs"; +import { SquareArrowOutUpRight } from "lucide-react"; +import { StoryCode } from "./helpers/StoryCode"; + +const meta: Meta = { + title: "Tokens /Icons", + parameters: { + layout: "fullscreen", + controls: { disable: true }, + }, +}; + +export default meta; + +// Icon categories with examples +const iconCategories = [ + { + name: "User & Authentication", + description: "Icons for user-related actions and authentication flows", + icons: [ + { component: User, name: "User", phosphorName: "User" }, + { component: UserPlus, name: "UserPlus", phosphorName: "UserPlus" }, + { component: UserCircle, name: "UserCircle", phosphorName: "UserCircle" }, + { component: Key, name: "Key", phosphorName: "Key" }, + { component: SignOut, name: "SignOut", phosphorName: "SignOut" }, + ], + }, + { + name: "Actions & Controls", + description: "Icons for common user actions and interface controls", + icons: [ + { component: Play, name: "Play", phosphorName: "Play" }, + { + component: ArrowClockwise, + name: "Refresh", + phosphorName: "ArrowClockwise", + }, + { component: FloppyDisk, name: "Save", phosphorName: "FloppyDisk" }, + { + component: ArrowCounterClockwise, + name: "Undo", + phosphorName: "ArrowCounterClockwise", + }, + { component: Redo, name: "Redo", phosphorName: "ArrowClockwise" }, + { component: PencilSimple, name: "Edit", phosphorName: "PencilSimple" }, + { component: Copy, name: "Copy", phosphorName: "Copy" }, + { component: Trash, name: "Delete", phosphorName: "Trash" }, + ], + }, + { + name: "Navigation & Layout", + description: "Icons for navigation, layout, and organizational elements", + icons: [ + { component: List, name: "Menu", phosphorName: "List" }, + { + component: SquaresFour, + name: "Dashboard", + phosphorName: "SquaresFour", + }, + { component: ArrowLeft, name: "ArrowLeft", phosphorName: "ArrowLeft" }, + { component: ArrowRight, name: "ArrowRight", phosphorName: "ArrowRight" }, + { component: Gear, name: "Settings", phosphorName: "Gear" }, + { component: Books, name: "Library", phosphorName: "Books" }, + ], + }, + { + name: "Content & Media", + description: "Icons for content types, media, and file operations", + icons: [ + { component: CloudArrowUp, name: "Upload", phosphorName: "CloudArrowUp" }, + { component: Download, name: "Download", phosphorName: "Download" }, + { component: Package, name: "Package", phosphorName: "Package" }, + { component: Cube, name: "Block", phosphorName: "Cube" }, + { component: FlowArrow, name: "Workflow", phosphorName: "FlowArrow" }, + ], + }, + { + name: "Feedback & Status", + description: "Icons for alerts, notifications, and status indicators", + icons: [ + { component: Warning, name: "Warning", phosphorName: "Warning" }, + { component: Info, name: "Info", phosphorName: "Info" }, + { component: Check, name: "Success", phosphorName: "Check" }, + { component: X, name: "Close", phosphorName: "X" }, + { component: Bell, name: "Notification", phosphorName: "Bell" }, + ], + }, + { + name: "Social & External", + description: "Icons for social media platforms and external links", + icons: [ + { component: GithubLogo, name: "GitHub", phosphorName: "GithubLogo" }, + { + component: LinkedinLogo, + name: "LinkedIn", + phosphorName: "LinkedinLogo", + }, + { component: XLogo, name: "X (Twitter)", phosphorName: "XLogo" }, + { + component: FacebookLogo, + name: "Facebook", + phosphorName: "FacebookLogo", + }, + { + component: InstagramLogo, + name: "Instagram", + phosphorName: "InstagramLogo", + }, + { component: YoutubeLogo, name: "YouTube", phosphorName: "YoutubeLogo" }, + ], + }, +]; + +export function AllVariants() { + return ( + <div className="space-y-12"> + {/* Icons System Documentation */} + <div className="space-y-8"> + <div> + <Text variant="h1" className="mb-4 text-zinc-800"> + Icons System + </Text> + <Text variant="large" className="text-zinc-600"> + Our icon system uses Phosphor Icons to provide a consistent, modern, + and comprehensive set of icons across all components. Phosphor + offers multiple weights and a cohesive design language that aligns + with our design principles. + </Text> + </div> + + <div className="grid gap-8 md:grid-cols-2"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Phosphor Icons + </Text> + <div className="space-y-4"> + <div className="rounded-lg border border-gray-200 p-4"> + <a + href="https://phosphoricons.com/" + target="_blank" + rel="noopener noreferrer" + className="mb-2 inline-flex flex-row items-center gap-1 text-base font-semibold text-blue-600 hover:underline" + > + Phosphor Icons Library{" "} + <SquareArrowOutUpRight className="inline-block h-3 w-3" /> + </a> + <Text variant="body" className="mb-2 text-zinc-600"> + A flexible icon family with multiple weights and styles + </Text> + <div className="font-mono text-sm text-zinc-800"> + @phosphor-icons/react → React components + </div> + </div> + <div className="rounded-lg border border-gray-200 p-4"> + <Text + variant="body-medium" + className="mb-2 font-semibold text-zinc-800" + > + Available Weights + </Text> + <Text variant="body" className="mb-2 text-zinc-600"> + Phosphor icons offer multiple weights - use the one specified + in your designs + </Text> + <div className="space-y-1 font-mono text-sm text-zinc-800"> + <div>regular (default), light, bold, fill, thin, duotone</div> + </div> + </div> + </div> + </div> + + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Usage Guidelines + </Text> + <div className="space-y-4"> + <div className="rounded-lg border-2 border-dashed border-blue-200 bg-blue-50 p-4"> + <Text + variant="body-medium" + className="mb-2 font-semibold text-blue-800" + > + ✅ Always Use Phosphor Icons + </Text> + <div className="space-y-2 text-blue-700"> + <Text variant="body"> + • Import from @phosphor-icons/react + </Text> + <Text variant="body"> + • Always match size and weight from Figma designs + </Text> + <Text variant="body"> + • Ensure icons have proper semantic meaning + </Text> + <Text variant="body"> + • Verify accessibility and color contrast + </Text> + </div> + </div> + <div> + <Text + variant="h3" + className="mb-2 text-base font-semibold text-zinc-800" + > + 🎨 Design Consistency + </Text> + <div className="space-y-2 text-zinc-600"> + <Text variant="body"> + • Follow the exact specifications from design team + </Text> + <Text variant="body"> + • Maintain consistency across similar UI elements + </Text> + <Text variant="body"> + • Consider accessibility requirements (minimum 16px) + </Text> + </div> + </div> + </div> + </div> + </div> + </div> + + {/* Design Matching */} + <div className="space-y-8"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Matching Design Specifications + </Text> + <Text variant="body" className="mb-6 text-zinc-600"> + When implementing icons, always reference the design specifications + provided by the design team to ensure proper sizing and weight. + </Text> + </div> + + <div className="rounded-lg border-2 border-dashed border-amber-200 bg-amber-50 p-6"> + <Text + variant="body-medium" + className="mb-3 font-semibold text-amber-800" + > + 🎨 Always Match Figma Designs + </Text> + <div className="space-y-3 text-amber-700"> + <Text variant="body"> + • Check the Figma designs for exact icon sizes (16px, 20px, 24px, + etc.) + </Text> + <Text variant="body"> + • Match the icon weight specified in designs (regular, bold, fill, + etc.) + </Text> + <Text variant="body"> + • Ensure color and opacity match the design specifications + </Text> + <Text variant="body"> + • Verify spacing and alignment with surrounding elements + </Text> + </div> + </div> + + <div className="flex items-center gap-8 rounded-lg border border-gray-200 p-6"> + <div className="flex items-center gap-4"> + <Alien size={16} className="text-zinc-600" /> + <Text variant="small" className="font-mono text-zinc-500"> + 16px + </Text> + </div> + <div className="flex items-center gap-4"> + <Alien size={20} className="text-zinc-600" /> + <Text variant="small" className="font-mono text-zinc-500"> + 20px + </Text> + </div> + <div className="flex items-center gap-4"> + <Alien size={24} className="text-zinc-600" /> + <Text variant="small" className="font-mono text-zinc-500"> + 24px + </Text> + </div> + <div className="flex items-center gap-4"> + <Alien size={32} className="text-zinc-600" /> + <Text variant="small" className="font-mono text-zinc-500"> + 32px + </Text> + </div> + </div> + </div> + + {/* Icon Categories */} + <div className="space-y-8"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Icon Categories + </Text> + <Text variant="body" className="mb-6 text-zinc-600"> + Our curated icon set organized by functional categories. Each icon + is carefully selected to maintain consistency and semantic clarity. + </Text> + </div> + + {iconCategories.map((category) => ( + <div key={category.name} className="space-y-4"> + <div> + <Text + variant="h3" + className="mb-1 text-lg font-semibold text-zinc-800" + > + {category.name} + </Text> + <Text variant="body" className="text-zinc-600"> + {category.description} + </Text> + </div> + <div className="grid grid-cols-2 gap-4 rounded-lg border border-gray-200 p-4 md:grid-cols-3 lg:grid-cols-6"> + {category.icons.map((icon) => ( + <div + key={icon.name} + className="flex flex-col items-center space-y-2 rounded-lg p-3 hover:bg-gray-50" + > + <icon.component size={24} className="text-zinc-600" /> + <Text + variant="small" + className="text-center font-mono text-zinc-500" + > + {icon.phosphorName} + </Text> + </div> + ))} + </div> + </div> + ))} + </div> + + {/* Usage Examples */} + <div className="space-y-8"> + <div> + <Text + variant="h2" + className="mb-2 text-xl font-semibold text-zinc-800" + > + Usage Examples + </Text> + <Text variant="body" className="mb-6 text-zinc-600"> + How to properly implement Phosphor icons in your React components. + </Text> + </div> + + <StoryCode + code={`// Import icons from Phosphor +import { User, Heart, Star, Bell } from "@phosphor-icons/react"; + +// Basic usage with default size (24px) +<User /> +<Heart /> + +// Custom sizes +<User size={16} /> // Small +<User size={20} /> // Default +<User size={24} /> // Large +<User size={32} /> // Extra large + +// With custom colors +<Heart className="text-red-500" /> +<Star className="text-yellow-500" /> + +// Different weights +<User weight="thin" /> // 1px stroke +<User weight="light" /> // 1.5px stroke +<User weight="regular" /> // 2px stroke (default) +<User weight="bold" /> // 2.5px stroke +<User weight="fill" /> // Filled version +<User weight="duotone" /> // Two-tone style + +// Interactive states +<Bell + size={20} + weight={hasNotifications ? "fill" : "regular"} + className={hasNotifications ? "text-blue-500" : "text-gray-400"} +/> + +// In buttons +<button className="flex items-center gap-2"> + <User size={16} /> + Profile +</button> + +// Responsive sizing with Tailwind +<User className="size-4 md:size-5 lg:size-6" />`} + /> + </div> + </div> + ); +} diff --git a/autogpt_platform/frontend/src/stories/Spacing.stories.tsx b/autogpt_platform/frontend/src/components/tokens/spacing.stories.tsx similarity index 97% rename from autogpt_platform/frontend/src/stories/Spacing.stories.tsx rename to autogpt_platform/frontend/src/components/tokens/spacing.stories.tsx index 59d97da3af..cbe3804a7e 100644 --- a/autogpt_platform/frontend/src/stories/Spacing.stories.tsx +++ b/autogpt_platform/frontend/src/components/tokens/spacing.stories.tsx @@ -1,10 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Text } from "@/components/_new/Text/Text"; -import { StoryCode } from "@/stories/helpers/StoryCode"; +import { Text } from "@/components/atoms/Text/Text"; +import type { Meta } from "@storybook/nextjs"; import { SquareArrowOutUpRight } from "lucide-react"; +import { StoryCode } from "./helpers/StoryCode"; const meta: Meta = { - title: "Design System/ Tokens /Spacing", + title: "Tokens /Spacing", parameters: { layout: "fullscreen", controls: { disable: true }, @@ -271,5 +271,3 @@ export function AllVariants() { </div> ); } - -type Story = StoryObj<typeof meta>; diff --git a/autogpt_platform/frontend/src/stories/Typography.stories.tsx b/autogpt_platform/frontend/src/components/tokens/typography.stories.tsx similarity index 96% rename from autogpt_platform/frontend/src/stories/Typography.stories.tsx rename to autogpt_platform/frontend/src/components/tokens/typography.stories.tsx index 493cab98c4..b51709140a 100644 --- a/autogpt_platform/frontend/src/stories/Typography.stories.tsx +++ b/autogpt_platform/frontend/src/components/tokens/typography.stories.tsx @@ -1,9 +1,9 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Text } from "@/components/_new/Text/Text"; -import { StoryCode } from "@/stories/helpers/StoryCode"; +import { Text } from "@/components/atoms/Text/Text"; +import type { Meta } from "@storybook/nextjs"; +import { StoryCode } from "./helpers/StoryCode"; const meta: Meta<typeof Text> = { - title: "Design System/ Tokens /Typography", + title: "Tokens /Typography", component: Text, parameters: { layout: "fullscreen", diff --git a/autogpt_platform/frontend/src/components/type-based-input.tsx b/autogpt_platform/frontend/src/components/type-based-input.tsx index fff5bf8d7c..5a628a5935 100644 --- a/autogpt_platform/frontend/src/components/type-based-input.tsx +++ b/autogpt_platform/frontend/src/components/type-based-input.tsx @@ -91,7 +91,7 @@ export const TypeBasedInput: FC< <Switch className="ml-auto" checked={!!value} - onCheckedChange={(checked) => onChange(checked)} + onCheckedChange={(checked: boolean) => onChange(checked)} {...props} /> </> @@ -145,7 +145,10 @@ export const TypeBasedInput: FC< schema.enum.length > 0 ) { innerInputElement = ( - <Select value={value ?? ""} onValueChange={(val) => onChange(val)}> + <Select + value={value ?? ""} + onValueChange={(val: string) => onChange(val)} + > <SelectTrigger className={cn( inputClasses, @@ -253,7 +256,7 @@ export function TimePicker({ value, onChange }: TimePickerProps) { <div className="flex flex-col items-center"> <Select value={hour} - onValueChange={(val) => changeTime(val, minute, meridiem)} + onValueChange={(val: string) => changeTime(val, minute, meridiem)} > <SelectTrigger className={cn("agpt-border-input ml-1 text-center", inputClasses)} @@ -277,7 +280,7 @@ export function TimePicker({ value, onChange }: TimePickerProps) { <div className="flex flex-col items-center"> <Select value={minute} - onValueChange={(val) => changeTime(hour, val, meridiem)} + onValueChange={(val: string) => changeTime(hour, val, meridiem)} > <SelectTrigger className={cn("agpt-border-input text-center", inputClasses)} @@ -297,7 +300,7 @@ export function TimePicker({ value, onChange }: TimePickerProps) { <div className="flex flex-col items-center"> <Select value={meridiem} - onValueChange={(val) => changeTime(hour, minute, val)} + onValueChange={(val: string) => changeTime(hour, minute, val)} > <SelectTrigger className={cn("agpt-border-input text-center", inputClasses)} @@ -357,12 +360,7 @@ interface FileInputProps { className?: string; } -const FileInput: FC<FileInputProps> = ({ - value, - placeholder, - onChange, - className, -}) => { +const FileInput: FC<FileInputProps> = ({ value, onChange, className }) => { const loadFile = (file: File) => { const reader = new FileReader(); reader.onload = (e) => { @@ -402,7 +400,7 @@ const FileInput: FC<FileInputProps> = ({ <Cross2Icon className="h-5 w-5 cursor-pointer text-black" onClick={() => { - inputRef.current && (inputRef.current.value = ""); + if (inputRef.current) inputRef.current.value = ""; onChange(""); }} /> diff --git a/autogpt_platform/frontend/src/components/ui/alert.stories.tsx b/autogpt_platform/frontend/src/components/ui/alert.stories.tsx deleted file mode 100644 index 178f103192..0000000000 --- a/autogpt_platform/frontend/src/components/ui/alert.stories.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Alert, AlertTitle, AlertDescription } from "./alert"; - -const meta = { - title: "UI/Alert", - component: Alert, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: ["default", "destructive"], - }, - }, -} satisfies Meta<typeof Alert>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: ( - <> - <AlertTitle>Default Alert</AlertTitle> - <AlertDescription> - This is a default alert description. - </AlertDescription> - </> - ), - }, -}; - -export const Destructive: Story = { - args: { - variant: "destructive", - children: ( - <> - <AlertTitle>Destructive Alert</AlertTitle> - <AlertDescription> - This is a destructive alert description. - </AlertDescription> - </> - ), - }, -}; - -export const WithIcon: Story = { - args: { - children: ( - <> - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - > - <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> - <line x1="12" y1="9" x2="12" y2="13" /> - <line x1="12" y1="17" x2="12.01" y2="17" /> - </svg> - <AlertTitle>Alert with Icon</AlertTitle> - <AlertDescription>This alert includes an icon.</AlertDescription> - </> - ), - }, -}; - -export const TitleOnly: Story = { - args: { - children: <AlertTitle>Alert with Title Only</AlertTitle>, - }, -}; - -export const DescriptionOnly: Story = { - args: { - children: ( - <AlertDescription> - This is an alert with only a description. - </AlertDescription> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/avatar.stories.tsx b/autogpt_platform/frontend/src/components/ui/avatar.stories.tsx deleted file mode 100644 index b03eaba988..0000000000 --- a/autogpt_platform/frontend/src/components/ui/avatar.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Avatar, AvatarImage, AvatarFallback } from "./avatar"; - -const meta = { - title: "UI/Avatar", - component: Avatar, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - // Add any specific controls for Avatar props here if needed - }, -} satisfies Meta<typeof Avatar>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />, - }, -}; - -export const WithFallback: Story = { - args: { - children: ( - <> - <AvatarImage src="/broken-image.jpg" alt="@shadcn" /> - <AvatarFallback>CN</AvatarFallback> - </> - ), - }, -}; - -export const FallbackOnly: Story = { - args: { - children: <AvatarFallback>JD</AvatarFallback>, - }, -}; - -export const CustomSize: Story = { - args: { - className: "h-16 w-16", - children: <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />, - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <AvatarFallback> - <span role="img" aria-label="Rocket"> - 🚀 - </span> - </AvatarFallback> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/badge.stories.tsx b/autogpt_platform/frontend/src/components/ui/badge.stories.tsx deleted file mode 100644 index f43e015011..0000000000 --- a/autogpt_platform/frontend/src/components/ui/badge.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Badge } from "./badge"; - -const meta = { - title: "UI/Badge", - component: Badge, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: ["default", "secondary", "destructive", "outline"], - }, - }, -} satisfies Meta<typeof Badge>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: "Badge", - }, -}; - -export const Secondary: Story = { - args: { - variant: "secondary", - children: "Secondary", - }, -}; - -export const Destructive: Story = { - args: { - variant: "destructive", - children: "Destructive", - }, -}; - -export const Outline: Story = { - args: { - variant: "outline", - children: "Outline", - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <> - <span className="mr-1">🚀</span> - Custom Content - </> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/button.stories.tsx b/autogpt_platform/frontend/src/components/ui/button.stories.tsx deleted file mode 100644 index 87feb82526..0000000000 --- a/autogpt_platform/frontend/src/components/ui/button.stories.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; -import { userEvent, within, expect } from "@storybook/test"; - -const meta = { - title: "UI/Button", - component: Button, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: [ - "default", - "destructive", - "outline", - "secondary", - "ghost", - "link", - ], - }, - size: { - control: "select", - options: ["default", "sm", "lg", "primary", "icon"], - }, - disabled: { - control: "boolean", - }, - asChild: { - control: "boolean", - }, - children: { - control: "text", - }, - onClick: { action: "clicked" }, - }, -} satisfies Meta<typeof Button>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: "Button", - }, -}; - -export const Interactive: Story = { - args: { - children: "Interactive Button", - }, - argTypes: { - onClick: { action: "clicked" }, - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const button = canvas.getByRole("button", { name: /Interactive Button/i }); - await userEvent.click(button); - await expect(button).toHaveFocus(); - }, -}; - -export const Variants: Story = { - render: (args) => ( - <div className="flex flex-wrap gap-2"> - <Button {...args} variant="default"> - Default - </Button> - <Button {...args} variant="destructive"> - Destructive - </Button> - <Button {...args} variant="outline"> - Outline - </Button> - <Button {...args} variant="secondary"> - Secondary - </Button> - <Button {...args} variant="ghost"> - Ghost - </Button> - <Button {...args} variant="link"> - Link - </Button> - </div> - ), - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const buttons = canvas.getAllByRole("button"); - await expect(buttons).toHaveLength(6); - for (const button of buttons) { - await userEvent.hover(button); - await expect(button).toHaveAttribute( - "class", - expect.stringContaining("hover:"), - ); - } - }, -}; - -export const Sizes: Story = { - render: (args) => ( - <div className="flex flex-wrap items-center gap-2"> - <Button {...args} size="sm"> - Small - </Button> - <Button {...args} size="default"> - Default - </Button> - <Button {...args} size="lg"> - Large - </Button> - <Button {...args} size="primary"> - Primary - </Button> - <Button {...args} size="icon"> - 🚀 - </Button> - </div> - ), - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const buttons = canvas.getAllByRole("button"); - await expect(buttons).toHaveLength(5); - const sizes = ["sm", "default", "lg", "primary", "icon"]; - const sizeClasses = [ - "h-8 rounded-md px-3 text-xs", - "h-9 px-4 py-2", - "h-10 rounded-md px-8", - "md:h-14 md:w-44 rounded-2xl h-10 w-28", - "h-9 w-9", - ]; - buttons.forEach(async (button, index) => { - await expect(button).toHaveAttribute( - "class", - expect.stringContaining(sizeClasses[index]), - ); - }); - }, -}; - -export const Disabled: Story = { - args: { - children: "Disabled Button", - disabled: true, - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const button = canvas.getByRole("button", { name: /Disabled Button/i }); - await expect(button).toBeDisabled(); - await expect(button).toHaveStyle("pointer-events: none"); - await expect(button).not.toHaveFocus(); - }, -}; - -export const WithIcon: Story = { - args: { - children: ( - <> - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - className="mr-2 h-4 w-4" - > - <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> - </svg> - Button with Icon - </> - ), - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const button = canvas.getByRole("button", { name: /Button with Icon/i }); - const icon = button.querySelector("svg"); - await expect(icon).toBeInTheDocument(); - await expect(button).toHaveTextContent("Button with Icon"); - }, -}; - -export const LoadingState: Story = { - args: { - children: "Loading...", - disabled: true, - }, - render: (args) => ( - <Button {...args}> - <svg - className="mr-2 h-4 w-4 animate-spin" - xmlns="http://www.w3.org/2000/svg" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - > - <path d="M21 12a9 9 0 1 1-6.219-8.56" /> - </svg> - {args.children} - </Button> - ), - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const button = canvas.getByRole("button", { name: /Loading.../i }); - await expect(button).toBeDisabled(); - const spinner = button.querySelector("svg"); - await expect(spinner).toHaveClass("animate-spin"); - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/calendar.stories.tsx b/autogpt_platform/frontend/src/components/ui/calendar.stories.tsx deleted file mode 100644 index 2414a7448a..0000000000 --- a/autogpt_platform/frontend/src/components/ui/calendar.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Calendar } from "./calendar"; - -const meta = { - title: "UI/Calendar", - component: Calendar, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - mode: { - control: "select", - options: ["single", "multiple", "range"], - }, - selected: { - control: "date", - }, - showOutsideDays: { - control: "boolean", - }, - }, -} satisfies Meta<typeof Calendar>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: {}, -}; - -export const SingleSelection: Story = { - args: { - mode: "single", - selected: new Date(), - }, -}; - -export const MultipleSelection: Story = { - args: { - mode: "multiple", - selected: [ - new Date(), - new Date(new Date().setDate(new Date().getDate() + 5)), - ], - }, -}; - -export const RangeSelection: Story = { - args: { - mode: "range", - selected: { - from: new Date(), - to: new Date(new Date().setDate(new Date().getDate() + 7)), - }, - }, -}; - -export const HideOutsideDays: Story = { - args: { - showOutsideDays: false, - }, -}; - -export const CustomClassName: Story = { - args: { - className: "border rounded-lg shadow-lg", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/card.stories.tsx b/autogpt_platform/frontend/src/components/ui/card.stories.tsx deleted file mode 100644 index cc804aad5c..0000000000 --- a/autogpt_platform/frontend/src/components/ui/card.stories.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Card, - CardHeader, - CardFooter, - CardTitle, - CardDescription, - CardContent, -} from "./card"; - -const meta = { - title: "UI/Card", - component: Card, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - // Add any specific controls for Card props here if needed - }, -} satisfies Meta<typeof Card>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: ( - <> - <CardHeader> - <CardTitle>Card Title</CardTitle> - <CardDescription>Card Description</CardDescription> - </CardHeader> - <CardContent> - <p>Card Content</p> - </CardContent> - <CardFooter> - <p>Card Footer</p> - </CardFooter> - </> - ), - }, -}; - -export const HeaderOnly: Story = { - args: { - children: ( - <CardHeader> - <CardTitle>Header Only Card</CardTitle> - <CardDescription>This card has only a header.</CardDescription> - </CardHeader> - ), - }, -}; - -export const ContentOnly: Story = { - args: { - children: ( - <CardContent> - <p>This card has only content.</p> - </CardContent> - ), - }, -}; - -export const FooterOnly: Story = { - args: { - children: ( - <CardFooter> - <p>This card has only a footer.</p> - </CardFooter> - ), - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <> - <CardHeader> - <CardTitle>Custom Content</CardTitle> - </CardHeader> - <CardContent> - <div className="flex h-40 items-center justify-center rounded-md bg-gray-100"> - <span role="img" aria-label="Rocket" style={{ fontSize: "3rem" }}> - 🚀 - </span> - </div> - </CardContent> - <CardFooter className="justify-between"> - <button className="rounded bg-blue-500 px-4 py-2 text-white"> - Action - </button> - <p>Footer text</p> - </CardFooter> - </> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/checkbox.stories.tsx b/autogpt_platform/frontend/src/components/ui/checkbox.stories.tsx deleted file mode 100644 index 3310c3f4c9..0000000000 --- a/autogpt_platform/frontend/src/components/ui/checkbox.stories.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Checkbox } from "./checkbox"; - -const meta = { - title: "UI/Checkbox", - component: Checkbox, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - checked: { - control: "boolean", - }, - disabled: { - control: "boolean", - }, - }, -} satisfies Meta<typeof Checkbox>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: {}, -}; - -export const Checked: Story = { - args: { - checked: true, - }, -}; - -export const Unchecked: Story = { - args: { - checked: false, - }, -}; - -export const Disabled: Story = { - args: { - disabled: true, - }, -}; - -export const DisabledChecked: Story = { - args: { - disabled: true, - checked: true, - }, -}; - -export const WithLabel: Story = { - args: {}, - render: (args) => ( - <div className="flex items-center space-x-2"> - <Checkbox id="terms" {...args} /> - <label - htmlFor="terms" - className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" - > - Accept terms and conditions - </label> - </div> - ), -}; - -export const CustomSize: Story = { - args: { - className: "h-6 w-6", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/collapsible.stories.tsx b/autogpt_platform/frontend/src/components/ui/collapsible.stories.tsx deleted file mode 100644 index dbd22d10c2..0000000000 --- a/autogpt_platform/frontend/src/components/ui/collapsible.stories.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { - Collapsible, - CollapsibleTrigger, - CollapsibleContent, -} from "./collapsible"; -import { Button } from "./button"; - -const meta = { - title: "UI/Collapsible", - component: Collapsible, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Collapsible>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => ( - <Collapsible> - <CollapsibleTrigger asChild> - <Button variant="outline">Toggle</Button> - </CollapsibleTrigger> - <CollapsibleContent className="mt-2 rounded bg-gray-100 p-4"> - <p>This is the collapsible content.</p> - </CollapsibleContent> - </Collapsible> - ), -}; - -export const OpenByDefault: Story = { - render: () => ( - <Collapsible defaultOpen> - <CollapsibleTrigger asChild> - <Button variant="outline">Toggle</Button> - </CollapsibleTrigger> - <CollapsibleContent className="mt-2 rounded bg-gray-100 p-4"> - <p>This collapsible is open by default.</p> - </CollapsibleContent> - </Collapsible> - ), -}; - -export const CustomTrigger: Story = { - render: () => ( - <Collapsible> - <CollapsibleTrigger asChild> - <Button variant="ghost"> - <span>🔽</span> Click to expand - </Button> - </CollapsibleTrigger> - <CollapsibleContent className="mt-2 rounded bg-gray-100 p-4"> - <p>Custom trigger example.</p> - </CollapsibleContent> - </Collapsible> - ), -}; - -export const NestedContent: Story = { - render: () => ( - <Collapsible> - <CollapsibleTrigger asChild> - <Button variant="outline">Toggle Nested Content</Button> - </CollapsibleTrigger> - <CollapsibleContent className="mt-2 rounded bg-gray-100 p-4"> - <h3 className="mb-2 font-bold">Main Content</h3> - <p className="mb-2">This is the main collapsible content.</p> - <Collapsible> - <CollapsibleTrigger asChild> - <Button variant="secondary" size="sm"> - Toggle Nested - </Button> - </CollapsibleTrigger> - <CollapsibleContent className="mt-2 rounded bg-gray-200 p-2"> - <p>This is nested collapsible content.</p> - </CollapsibleContent> - </Collapsible> - </CollapsibleContent> - </Collapsible> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/command.stories.tsx b/autogpt_platform/frontend/src/components/ui/command.stories.tsx deleted file mode 100644 index 2d0aec23e3..0000000000 --- a/autogpt_platform/frontend/src/components/ui/command.stories.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, -} from "./command"; - -const meta = { - title: "UI/Command", - component: Command, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Command>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => ( - <Command> - <CommandInput placeholder="Type a command or search..." /> - <CommandList> - <CommandEmpty>No results found.</CommandEmpty> - <CommandGroup heading="Suggestions"> - <CommandItem> - <span>Calendar</span> - </CommandItem> - <CommandItem> - <span>Search Emoji</span> - </CommandItem> - <CommandItem> - <span>Calculator</span> - </CommandItem> - </CommandGroup> - <CommandGroup heading="Settings"> - <CommandItem> - <span>Profile</span> - <CommandShortcut>⌘P</CommandShortcut> - </CommandItem> - <CommandItem> - <span>Billing</span> - <CommandShortcut>⌘B</CommandShortcut> - </CommandItem> - <CommandItem> - <span>Settings</span> - <CommandShortcut>⌘S</CommandShortcut> - </CommandItem> - </CommandGroup> - </CommandList> - </Command> - ), -}; - -export const WithDialog: Story = { - render: () => ( - <CommandDialog> - <CommandInput placeholder="Type a command or search..." /> - <CommandList> - <CommandEmpty>No results found.</CommandEmpty> - <CommandGroup heading="Suggestions"> - <CommandItem>Calendar</CommandItem> - <CommandItem>Search Emoji</CommandItem> - <CommandItem>Calculator</CommandItem> - </CommandGroup> - </CommandList> - </CommandDialog> - ), -}; - -export const CustomContent: Story = { - render: () => ( - <Command className="rounded-lg border shadow-md"> - <CommandInput placeholder="Search for fruit..." /> - <CommandList> - <CommandEmpty>No fruits found.</CommandEmpty> - <CommandGroup heading="Fruits"> - <CommandItem> - <span>🍎 Apple</span> - </CommandItem> - <CommandItem> - <span>🍌 Banana</span> - </CommandItem> - <CommandItem> - <span>🍊 Orange</span> - </CommandItem> - </CommandGroup> - </CommandList> - </Command> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/command.tsx b/autogpt_platform/frontend/src/components/ui/command.tsx index f8c8c7f6bc..8d476a3257 100644 --- a/autogpt_platform/frontend/src/components/ui/command.tsx +++ b/autogpt_platform/frontend/src/components/ui/command.tsx @@ -23,7 +23,9 @@ const Command = React.forwardRef< )); Command.displayName = CommandPrimitive.displayName; -interface CommandDialogProps extends DialogProps {} +interface CommandDialogProps extends DialogProps { + children: React.ReactNode; +} const CommandDialog = ({ children, ...props }: CommandDialogProps) => { return ( diff --git a/autogpt_platform/frontend/src/components/ui/data-table.stories.tsx b/autogpt_platform/frontend/src/components/ui/data-table.stories.tsx deleted file mode 100644 index 7fb34630aa..0000000000 --- a/autogpt_platform/frontend/src/components/ui/data-table.stories.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { DataTable } from "./data-table"; -import { Button } from "./button"; - -const meta = { - title: "UI/DataTable", - component: DataTable, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof DataTable>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const sampleData = [ - { id: 1, name: "John Doe", age: 30, city: "New York" }, - { id: 2, name: "Jane Smith", age: 25, city: "Los Angeles" }, - { id: 3, name: "Bob Johnson", age: 35, city: "Chicago" }, -]; - -const sampleColumns = [ - { accessorKey: "name", header: "Name" }, - { accessorKey: "age", header: "Age" }, - { accessorKey: "city", header: "City" }, -]; - -export const Default: Story = { - args: { - columns: sampleColumns, - data: sampleData, - filterPlaceholder: "Filter by name...", - filterColumn: "name", - }, -}; - -export const WithGlobalActions: Story = { - args: { - ...Default.args, - globalActions: [ - { - component: <Button>Delete Selected</Button>, - action: async (rows) => { - console.log("Deleting:", rows); - }, - }, - ], - }, -}; - -export const NoResults: Story = { - args: { - ...Default.args, - data: [], - }, -}; - -export const CustomFilterPlaceholder: Story = { - args: { - ...Default.args, - filterPlaceholder: "Search for a user...", - }, -}; - -export const WithoutFilter: Story = { - args: { - columns: sampleColumns, - data: sampleData, - filterPlaceholder: "", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/dialog.stories.tsx b/autogpt_platform/frontend/src/components/ui/dialog.stories.tsx deleted file mode 100644 index dcbfe943e2..0000000000 --- a/autogpt_platform/frontend/src/components/ui/dialog.stories.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "./dialog"; - -const meta = { - title: "UI/Dialog", - component: Dialog, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Dialog>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => ( - <Dialog> - <DialogTrigger asChild> - <Button variant="outline">Open Dialog</Button> - </DialogTrigger> - <DialogContent> - <DialogHeader> - <DialogTitle>Are you sure?</DialogTitle> - <DialogDescription> - This action cannot be undone. This will permanently delete your - account and remove your data from our servers. - </DialogDescription> - </DialogHeader> - <DialogFooter> - <Button type="submit">Confirm</Button> - </DialogFooter> - </DialogContent> - </Dialog> - ), -}; - -export const WithForm: Story = { - render: () => ( - <Dialog> - <DialogTrigger asChild> - <Button>Edit Profile</Button> - </DialogTrigger> - <DialogContent> - <DialogHeader> - <DialogTitle>Edit profile</DialogTitle> - <DialogDescription> - Make changes to your profile here. Click save when you're done. - </DialogDescription> - </DialogHeader> - <div className="grid gap-4 py-4"> - <div className="grid grid-cols-4 items-center gap-4"> - <label htmlFor="name" className="text-right"> - Name - </label> - <input id="name" className="col-span-3" /> - </div> - <div className="grid grid-cols-4 items-center gap-4"> - <label htmlFor="username" className="text-right"> - Username - </label> - <input id="username" className="col-span-3" /> - </div> - </div> - <DialogFooter> - <Button type="submit">Save changes</Button> - </DialogFooter> - </DialogContent> - </Dialog> - ), -}; - -export const CustomContent: Story = { - render: () => ( - <Dialog> - <DialogTrigger asChild> - <Button>Show Custom Dialog</Button> - </DialogTrigger> - <DialogContent> - <DialogHeader> - <DialogTitle>Custom Content</DialogTitle> - </DialogHeader> - <div className="flex items-center justify-center p-6"> - <span className="text-4xl">🎉</span> - <p className="ml-4">This is a custom dialog content!</p> - </div> - <DialogFooter> - <Button>Close</Button> - </DialogFooter> - </DialogContent> - </Dialog> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/dropdown-menu.stories.tsx b/autogpt_platform/frontend/src/components/ui/dropdown-menu.stories.tsx deleted file mode 100644 index 71130935aa..0000000000 --- a/autogpt_platform/frontend/src/components/ui/dropdown-menu.stories.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "./dropdown-menu"; - -const meta = { - title: "UI/DropdownMenu", - component: DropdownMenu, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof DropdownMenu>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="outline">Open Menu</Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuLabel>My Account</DropdownMenuLabel> - <DropdownMenuSeparator /> - <DropdownMenuItem>Profile</DropdownMenuItem> - <DropdownMenuItem>Billing</DropdownMenuItem> - <DropdownMenuItem>Team</DropdownMenuItem> - <DropdownMenuItem>Subscription</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ), -}; - -export const WithDisabledItem: Story = { - render: () => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="outline">Open Menu</Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuItem>Profile</DropdownMenuItem> - <DropdownMenuItem>Billing</DropdownMenuItem> - <DropdownMenuItem disabled>Team (disabled)</DropdownMenuItem> - <DropdownMenuItem>Subscription</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ), -}; - -export const WithIcons: Story = { - render: () => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="outline">Open Menu</Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuItem> - <span className="mr-2">👤</span> Profile - </DropdownMenuItem> - <DropdownMenuItem> - <span className="mr-2">💳</span> Billing - </DropdownMenuItem> - <DropdownMenuItem> - <span className="mr-2">👥</span> Team - </DropdownMenuItem> - <DropdownMenuItem> - <span className="mr-2">📅</span> Subscription - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ), -}; - -export const NestedDropdowns: Story = { - render: () => ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="outline">Open Menu</Button> - </DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuItem>Profile</DropdownMenuItem> - <DropdownMenuItem>Billing</DropdownMenuItem> - <DropdownMenu> - <DropdownMenuTrigger className="w-full">Team</DropdownMenuTrigger> - <DropdownMenuContent> - <DropdownMenuItem>Add Member</DropdownMenuItem> - <DropdownMenuItem>Remove Member</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - <DropdownMenuItem>Subscription</DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/icons.stories.tsx b/autogpt_platform/frontend/src/components/ui/icons.stories.tsx deleted file mode 100644 index f9aa3f8c6d..0000000000 --- a/autogpt_platform/frontend/src/components/ui/icons.stories.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { - IconUser, - IconUserPlus, - IconKey, - IconKeyPlus, - IconWorkFlow, - IconPlay, - IconSquare, - IconSquareActivity, - IconRefresh, - IconSave, - IconUndo2, - IconRedo2, - IconToyBrick, - IconCircleAlert, - IconCircleUser, - IconPackage2, - IconMegaphone, - IconMenu, - IconCoin, - IconEdit, - IconLogOut, - IconSettings, - IconLayoutDashboard, - IconUploadCloud, - IconMedium, - IconYoutube, - IconTiktok, - IconGlobe, - IconBuilder, - IconLibrary, - IconGithub, - IconLinkedin, - IconFacebook, - IconX, - IconInstagram, - IconLeftArrow, - IconRightArrow, -} from "./icons"; - -const meta = { - title: "UI/Icons", - component: IconUser, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - size: { - control: "select", - options: ["default", "sm", "lg"], - }, - className: { control: "text" }, - }, -} satisfies Meta<typeof IconUser>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const IconWrapper = ({ children }: { children: React.ReactNode }) => ( - <div className="flex flex-wrap gap-4">{children}</div> -); - -export const AllIcons: Story = { - render: (args) => ( - <IconWrapper> - <IconUser {...args} /> - <IconUserPlus {...args} /> - <IconKey {...args} /> - <IconKeyPlus {...args} /> - <IconWorkFlow {...args} /> - <IconPlay {...args} /> - <IconSquare {...args} /> - <IconSquareActivity {...args} /> - <IconRefresh {...args} /> - <IconSave {...args} /> - <IconUndo2 {...args} /> - <IconRedo2 {...args} /> - <IconToyBrick {...args} /> - <IconCircleAlert {...args} /> - <IconCircleUser {...args} /> - <IconPackage2 {...args} /> - <IconMegaphone {...args} /> - <IconMenu {...args} /> - <IconCoin {...args} /> - <IconEdit {...args} /> - <IconLogOut {...args} /> - <IconSettings {...args} /> - <IconLayoutDashboard {...args} /> - <IconUploadCloud {...args} /> - <IconMedium {...args} /> - <IconYoutube {...args} /> - <IconTiktok {...args} /> - <IconGlobe {...args} /> - <IconBuilder {...args} /> - <IconLibrary {...args} /> - <IconGithub {...args} /> - <IconLinkedin {...args} /> - <IconFacebook {...args} /> - <IconX {...args} /> - <IconInstagram {...args} /> - <IconLeftArrow {...args} /> - <IconRightArrow {...args} /> - </IconWrapper> - ), -}; - -export const DefaultSize: Story = { - args: { - size: "default", - }, -}; - -export const SmallSize: Story = { - args: { - size: "sm", - }, -}; - -export const LargeSize: Story = { - args: { - size: "lg", - }, -}; - -export const CustomColor: Story = { - args: { - className: "text-blue-500", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/icons.tsx b/autogpt_platform/frontend/src/components/ui/icons.tsx index bc14674bee..5592ad05cf 100644 --- a/autogpt_platform/frontend/src/components/ui/icons.tsx +++ b/autogpt_platform/frontend/src/components/ui/icons.tsx @@ -1842,11 +1842,10 @@ export function getIconForSocial( url: string, props: IconProps, ): React.ReactNode { - const lowerCaseUrl = url.toLowerCase(); let host; try { host = new URL(url).host; - } catch (e) { + } catch { return <IconGlobe {...props} />; } diff --git a/autogpt_platform/frontend/src/components/ui/input.stories.tsx b/autogpt_platform/frontend/src/components/ui/input.stories.tsx deleted file mode 100644 index fdfc589282..0000000000 --- a/autogpt_platform/frontend/src/components/ui/input.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Input } from "./input"; - -const meta = { - title: "UI/Input", - component: Input, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - type: { - control: "select", - options: ["text", "password", "email", "number", "file"], - }, - placeholder: { control: "text" }, - disabled: { control: "boolean" }, - }, -} satisfies Meta<typeof Input>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - placeholder: "Enter text...", - }, -}; - -export const Password: Story = { - args: { - type: "password", - placeholder: "Enter password...", - }, -}; - -export const Email: Story = { - args: { - type: "email", - placeholder: "Enter email...", - }, -}; - -export const Number: Story = { - args: { - type: "number", - placeholder: "Enter number...", - }, -}; - -export const File: Story = { - args: { - type: "file", - }, -}; - -export const Disabled: Story = { - args: { - placeholder: "Disabled input", - disabled: true, - }, -}; - -export const WithCustomClassName: Story = { - args: { - placeholder: "Custom class", - className: "border-2 border-blue-500", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/input.tsx b/autogpt_platform/frontend/src/components/ui/input.tsx index 01b0b6cc38..ae339d0a26 100644 --- a/autogpt_platform/frontend/src/components/ui/input.tsx +++ b/autogpt_platform/frontend/src/components/ui/input.tsx @@ -2,8 +2,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; -export interface InputProps - extends React.InputHTMLAttributes<HTMLInputElement> {} +export type InputProps = React.InputHTMLAttributes<HTMLInputElement>; const Input = React.forwardRef<HTMLInputElement, InputProps>( ({ className, type, ...props }, ref) => { diff --git a/autogpt_platform/frontend/src/components/ui/label.stories.tsx b/autogpt_platform/frontend/src/components/ui/label.stories.tsx deleted file mode 100644 index 2d18f60dcb..0000000000 --- a/autogpt_platform/frontend/src/components/ui/label.stories.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Label } from "./label"; - -const meta = { - title: "UI/Label", - component: Label, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - htmlFor: { control: "text" }, - }, -} satisfies Meta<typeof Label>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: "Default Label", - }, -}; - -export const WithHtmlFor: Story = { - args: { - htmlFor: "example-input", - children: "Label with htmlFor", - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <> - <span className="mr-1">📝</span> - Custom Label Content - </> - ), - }, -}; - -export const WithClassName: Story = { - args: { - className: "text-blue-500 font-bold", - children: "Styled Label", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/multiselect.stories.tsx b/autogpt_platform/frontend/src/components/ui/multiselect.stories.tsx deleted file mode 100644 index 2fe16feef1..0000000000 --- a/autogpt_platform/frontend/src/components/ui/multiselect.stories.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { - MultiSelector, - MultiSelectorTrigger, - MultiSelectorInput, - MultiSelectorContent, - MultiSelectorList, - MultiSelectorItem, -} from "./multiselect"; - -const meta = { - title: "UI/MultiSelector", - component: MultiSelector, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - loop: { - control: "boolean", - }, - values: { - control: "object", - }, - onValuesChange: { action: "onValuesChange" }, - }, -} satisfies Meta<typeof MultiSelector>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const MultiSelectorExample = (args: any) => { - const [values, setValues] = React.useState<string[]>(args.values || []); - - return ( - <MultiSelector values={values} onValuesChange={setValues} {...args}> - <MultiSelectorTrigger> - <MultiSelectorInput placeholder="Select items..." /> - </MultiSelectorTrigger> - <MultiSelectorContent> - <MultiSelectorList> - <MultiSelectorItem value="apple">Apple</MultiSelectorItem> - <MultiSelectorItem value="banana">Banana</MultiSelectorItem> - <MultiSelectorItem value="cherry">Cherry</MultiSelectorItem> - <MultiSelectorItem value="date">Date</MultiSelectorItem> - <MultiSelectorItem value="elderberry">Elderberry</MultiSelectorItem> - </MultiSelectorList> - </MultiSelectorContent> - </MultiSelector> - ); -}; - -export const Default: Story = { - render: (args) => <MultiSelectorExample {...args} />, - args: { - values: [], - onValuesChange: (value: string[]) => {}, - }, -}; - -export const WithLoop: Story = { - render: (args) => <MultiSelectorExample {...args} />, - args: { - values: [], - onValuesChange: (value: string[]) => {}, - loop: true, - }, -}; - -export const WithInitialValues: Story = { - render: (args) => <MultiSelectorExample {...args} />, - args: { - values: ["apple", "banana"], - onValuesChange: (value: string[]) => {}, - }, -}; - -export const WithDisabledItem: Story = { - render: (args) => ( - <MultiSelectorExample {...args}> - <MultiSelectorTrigger> - <MultiSelectorInput placeholder="Select items..." /> - </MultiSelectorTrigger> - <MultiSelectorContent> - <MultiSelectorList> - <MultiSelectorItem value="apple">Apple</MultiSelectorItem> - <MultiSelectorItem value="banana">Banana</MultiSelectorItem> - <MultiSelectorItem value="cherry" disabled> - Cherry (Disabled) - </MultiSelectorItem> - <MultiSelectorItem value="date">Date</MultiSelectorItem> - <MultiSelectorItem value="elderberry">Elderberry</MultiSelectorItem> - </MultiSelectorList> - </MultiSelectorContent> - </MultiSelectorExample> - ), - args: { - values: [], - onValuesChange: (value: string[]) => {}, - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/popover.stories.tsx b/autogpt_platform/frontend/src/components/ui/popover.stories.tsx deleted file mode 100644 index 901e551493..0000000000 --- a/autogpt_platform/frontend/src/components/ui/popover.stories.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { Popover, PopoverTrigger, PopoverContent } from "./popover"; -import { Button } from "./button"; - -const meta = { - title: "UI/Popover", - component: Popover, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Popover>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const PopoverExample = (args: any) => ( - <Popover> - <PopoverTrigger asChild> - <Button variant="outline">Open Popover</Button> - </PopoverTrigger> - <PopoverContent> - <div className="text-sm"> - <h3 className="mb-1 font-medium">Popover Content</h3> - <p>This is the content of the popover.</p> - </div> - </PopoverContent> - </Popover> -); - -export const Default: Story = { - render: () => <PopoverExample />, -}; - -export const AlignStart: Story = { - render: () => ( - <Popover> - <PopoverTrigger asChild> - <Button variant="outline">Open Popover</Button> - </PopoverTrigger> - <PopoverContent align="start"> - <div className="text-sm"> - <h3 className="mb-1 font-medium">Popover Content</h3> - <p>This is the content of the popover.</p> - </div> - </PopoverContent> - </Popover> - ), -}; - -export const AlignEnd: Story = { - render: () => ( - <Popover> - <PopoverTrigger asChild> - <Button variant="outline">Open Popover</Button> - </PopoverTrigger> - <PopoverContent align="end"> - <div className="text-sm"> - <h3 className="mb-1 font-medium">Popover Content</h3> - <p>This is the content of the popover.</p> - </div> - </PopoverContent> - </Popover> - ), -}; - -export const CustomOffset: Story = { - render: () => ( - <Popover> - <PopoverTrigger asChild> - <Button variant="outline">Open Popover</Button> - </PopoverTrigger> - <PopoverContent sideOffset={10}> - <div className="text-sm"> - <h3 className="mb-1 font-medium">Popover Content</h3> - <p>This is the content of the popover.</p> - </div> - </PopoverContent> - </Popover> - ), -}; - -export const CustomContent: Story = { - render: () => ( - <Popover> - <PopoverTrigger asChild> - <Button variant="outline">Custom Popover</Button> - </PopoverTrigger> - <PopoverContent> - <div className="text-sm"> - <h3 className="mb-1 font-medium">Custom Content</h3> - <p>This popover has custom content.</p> - <Button className="mt-2" size="sm"> - Action Button - </Button> - </div> - </PopoverContent> - </Popover> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/render.stories.tsx b/autogpt_platform/frontend/src/components/ui/render.stories.tsx deleted file mode 100644 index a453f79124..0000000000 --- a/autogpt_platform/frontend/src/components/ui/render.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { ContentRenderer } from "./render"; - -const meta = { - title: "UI/ContentRenderer", - component: ContentRenderer, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - value: { control: "text" }, - truncateLongData: { control: "boolean" }, - }, -} satisfies Meta<typeof ContentRenderer>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Text: Story = { - args: { - value: "This is a simple text content.", - }, -}; - -export const LongText: Story = { - args: { - value: - "This is a very long text that will be truncated when the truncateLongData prop is set to true. It contains more than 100 characters to demonstrate the truncation feature.", - truncateLongData: true, - }, -}; - -export const Image: Story = { - args: { - value: "https://example.com/image.jpg", - }, -}; - -export const Video: Story = { - args: { - value: "https://example.com/video.mp4", - }, -}; - -export const YouTubeVideo: Story = { - args: { - value: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - }, -}; - -export const JsonObject: Story = { - args: { - value: { key: "value", nested: { array: [1, 2, 3] } }, - }, -}; - -export const TruncatedJsonObject: Story = { - args: { - value: { - key: "value", - nested: { array: [1, 2, 3] }, - longText: - "This is a very long text that will be truncated when rendered as part of the JSON object.", - }, - truncateLongData: true, - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/render.tsx b/autogpt_platform/frontend/src/components/ui/render.tsx index a88a234038..cd484d942c 100644 --- a/autogpt_platform/frontend/src/components/ui/render.tsx +++ b/autogpt_platform/frontend/src/components/ui/render.tsx @@ -1,7 +1,6 @@ "use client"; import * as React from "react"; -import Image from "next/image"; const getYouTubeVideoId = (url: string) => { const regExp = diff --git a/autogpt_platform/frontend/src/components/ui/scroll-area.stories.tsx b/autogpt_platform/frontend/src/components/ui/scroll-area.stories.tsx deleted file mode 100644 index 0e2ee1bfb1..0000000000 --- a/autogpt_platform/frontend/src/components/ui/scroll-area.stories.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { ScrollArea } from "./scroll-area"; - -const meta = { - title: "UI/ScrollArea", - component: ScrollArea, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - className: { control: "text" }, - }, -} satisfies Meta<typeof ScrollArea>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - className: "h-[200px] w-[350px] rounded-md border p-4", - children: ( - <div> - <p className="mb-4">This is a scrollable area with some content.</p> - {Array(20) - .fill(0) - .map((_, i) => ( - <div key={i} className="mb-2"> - Item {i + 1} - </div> - ))} - </div> - ), - }, -}; - -export const HorizontalScroll: Story = { - args: { - className: "h-[100px] w-[350px] rounded-md border", - children: ( - <div className="flex p-4"> - {Array(20) - .fill(0) - .map((_, i) => ( - <div - key={i} - className="mr-4 flex h-16 w-16 items-center justify-center rounded-md border" - > - {i + 1} - </div> - ))} - </div> - ), - }, -}; - -export const NestedScrollAreas: Story = { - args: { - className: "h-[300px] w-[350px] rounded-md border p-4", - children: ( - <div> - <h4 className="mb-4 text-sm font-medium leading-none"> - Outer Scroll Area - </h4> - {Array(3) - .fill(0) - .map((_, i) => ( - <div key={i} className="mb-4"> - <p className="mb-2">Section {i + 1}</p> - <ScrollArea className="h-[100px] w-[300px] rounded-md border p-4"> - <div> - <h5 className="mb-2 text-sm font-medium leading-none"> - Inner Scroll Area - </h5> - {Array(10) - .fill(0) - .map((_, j) => ( - <div key={j} className="mb-2"> - Nested Item {j + 1} - </div> - ))} - </div> - </ScrollArea> - </div> - ))} - </div> - ), - }, -}; - -export const CustomScrollbarColors: Story = { - args: { - className: "h-[200px] w-[350px] rounded-md border p-4", - children: ( - <div> - <p className="mb-4">Customized scrollbar colors.</p> - {Array(20) - .fill(0) - .map((_, i) => ( - <div key={i} className="mb-2"> - Item {i + 1} - </div> - ))} - </div> - ), - }, - parameters: { - backgrounds: { default: "dark" }, - }, - decorators: [ - (Story) => ( - <div className="dark"> - <style> - {` - .dark .custom-scrollbar [data-radix-scroll-area-thumb] { - background-color: rgba(255, 255, 255, 0.2); - } - `} - </style> - <Story /> - </div> - ), - ], -}; diff --git a/autogpt_platform/frontend/src/components/ui/select.stories.tsx b/autogpt_platform/frontend/src/components/ui/select.stories.tsx deleted file mode 100644 index 0c6995779a..0000000000 --- a/autogpt_platform/frontend/src/components/ui/select.stories.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Select, - SelectTrigger, - SelectValue, - SelectContent, - SelectItem, -} from "./select"; - -const meta = { - title: "UI/Select", - component: Select, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - disabled: { - control: "boolean", - }, - }, -} satisfies Meta<typeof Select>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const SelectExample = (args: any) => ( - <Select {...args}> - <SelectTrigger className="w-[180px]"> - <SelectValue placeholder="Select an option" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="option1">Option 1</SelectItem> - <SelectItem value="option2">Option 2</SelectItem> - <SelectItem value="option3">Option 3</SelectItem> - </SelectContent> - </Select> -); - -export const Default: Story = { - render: (args) => <SelectExample {...args} />, -}; - -export const Disabled: Story = { - render: (args) => <SelectExample {...args} />, - args: { - disabled: true, - }, -}; - -export const WithPlaceholder: Story = { - render: (args) => ( - <Select {...args}> - <SelectTrigger className="w-[180px]"> - <SelectValue placeholder="Choose an option" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="option1">Option 1</SelectItem> - <SelectItem value="option2">Option 2</SelectItem> - <SelectItem value="option3">Option 3</SelectItem> - </SelectContent> - </Select> - ), -}; - -export const WithDefaultValue: Story = { - render: (args) => ( - <Select defaultValue="option2" {...args}> - <SelectTrigger className="w-[180px]"> - <SelectValue /> - </SelectTrigger> - <SelectContent> - <SelectItem value="option1">Option 1</SelectItem> - <SelectItem value="option2">Option 2</SelectItem> - <SelectItem value="option3">Option 3</SelectItem> - </SelectContent> - </Select> - ), -}; - -export const CustomTriggerWidth: Story = { - render: (args) => ( - <Select {...args}> - <SelectTrigger className="w-[250px]"> - <SelectValue placeholder="Select an option" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="option1">Option 1</SelectItem> - <SelectItem value="option2">Option 2</SelectItem> - <SelectItem value="option3">Option 3</SelectItem> - </SelectContent> - </Select> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/separator.stories.tsx b/autogpt_platform/frontend/src/components/ui/separator.stories.tsx deleted file mode 100644 index 2fb3a5f8f9..0000000000 --- a/autogpt_platform/frontend/src/components/ui/separator.stories.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; - -import { Separator } from "./separator"; - -const meta = { - title: "UI/Separator", - component: Separator, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - orientation: { - control: "select", - options: ["horizontal", "vertical"], - }, - className: { control: "text" }, - }, -} satisfies Meta<typeof Separator>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: {}, -}; - -export const Horizontal: Story = { - args: { - orientation: "horizontal", - }, - decorators: [ - (Story) => ( - <div style={{ width: "300px" }}> - <div>Above</div> - <Story /> - <div>Below</div> - </div> - ), - ], -}; - -export const Vertical: Story = { - args: { - orientation: "vertical", - }, - decorators: [ - (Story) => ( - <div style={{ height: "100px", display: "flex", alignItems: "center" }}> - <div>Left</div> - <Story /> - <div>Right</div> - </div> - ), - ], -}; - -export const CustomStyle: Story = { - args: { - className: "bg-red-500", - }, - decorators: [ - (Story) => ( - <div style={{ width: "300px" }}> - <div>Above</div> - <Story /> - <div>Below</div> - </div> - ), - ], -}; - -export const WithContent: Story = { - render: (args) => ( - <div className="space-y-1"> - <h4 className="text-sm font-medium leading-none">Radix Primitives</h4> - <p className="text-sm text-muted-foreground"> - An open-source UI component library. - </p> - <Separator {...args} /> - <div className="flex h-5 items-center space-x-4 text-sm"> - <div>Blog</div> - <Separator orientation="vertical" /> - <div>Docs</div> - <Separator orientation="vertical" /> - <div>Source</div> - </div> - </div> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/sheet.stories.tsx b/autogpt_platform/frontend/src/components/ui/sheet.stories.tsx deleted file mode 100644 index aa78d73367..0000000000 --- a/autogpt_platform/frontend/src/components/ui/sheet.stories.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Sheet, - SheetTrigger, - SheetContent, - SheetHeader, - SheetFooter, - SheetTitle, - SheetDescription, -} from "./sheet"; -import { Button } from "./button"; - -const meta = { - title: "UI/Sheet", - component: Sheet, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Sheet>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const SheetDemo = ({ side }: { side: "top" | "right" | "bottom" | "left" }) => ( - <Sheet> - <SheetTrigger asChild> - <Button variant="outline">Open Sheet</Button> - </SheetTrigger> - <SheetContent side={side}> - <SheetHeader> - <SheetTitle>Sheet Title</SheetTitle> - <SheetDescription> - This is a description of the sheet content. - </SheetDescription> - </SheetHeader> - <div className="py-4">Sheet content goes here.</div> - <SheetFooter> - <Button>Save changes</Button> - </SheetFooter> - </SheetContent> - </Sheet> -); - -export const Default: Story = { - render: () => <SheetDemo side="right" />, -}; - -export const Left: Story = { - render: () => <SheetDemo side="left" />, -}; - -export const Top: Story = { - render: () => <SheetDemo side="top" />, -}; - -export const Bottom: Story = { - render: () => <SheetDemo side="bottom" />, -}; - -export const CustomContent: Story = { - render: () => ( - <Sheet> - <SheetTrigger asChild> - <Button variant="outline">Open Custom Sheet</Button> - </SheetTrigger> - <SheetContent> - <SheetHeader> - <SheetTitle>Custom Sheet</SheetTitle> - </SheetHeader> - <div className="py-4"> - <p>This sheet has custom content.</p> - <ul className="list-disc pl-4 pt-2"> - <li>Item 1</li> - <li>Item 2</li> - <li>Item 3</li> - </ul> - </div> - </SheetContent> - </Sheet> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/switch.stories.tsx b/autogpt_platform/frontend/src/components/ui/switch.stories.tsx deleted file mode 100644 index 20f1b48ea6..0000000000 --- a/autogpt_platform/frontend/src/components/ui/switch.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Switch } from "./switch"; - -const meta = { - title: "UI/Switch", - component: Switch, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - checked: { control: "boolean" }, - disabled: { control: "boolean" }, - }, -} satisfies Meta<typeof Switch>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: {}, -}; - -export const Checked: Story = { - args: { - checked: true, - }, -}; - -export const Disabled: Story = { - args: { - disabled: true, - }, -}; - -export const CheckedAndDisabled: Story = { - args: { - checked: true, - disabled: true, - }, -}; - -export const WithLabel: Story = { - render: (args) => ( - <div className="flex items-center space-x-2"> - <Switch {...args} /> - <label>Toggle me</label> - </div> - ), -}; - -export const CustomSized: Story = { - render: (args) => <Switch className="h-6 w-11" {...args} />, -}; diff --git a/autogpt_platform/frontend/src/components/ui/table.stories.tsx b/autogpt_platform/frontend/src/components/ui/table.stories.tsx deleted file mode 100644 index 9a51b5a971..0000000000 --- a/autogpt_platform/frontend/src/components/ui/table.stories.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Table, - TableHeader, - TableBody, - TableFooter, - TableHead, - TableRow, - TableCell, - TableCaption, -} from "./table"; - -const meta = { - title: "UI/Table", - component: Table, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Table>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => ( - <Table> - <TableCaption>A list of your recent invoices.</TableCaption> - <TableHeader> - <TableRow> - <TableHead>Invoice</TableHead> - <TableHead>Status</TableHead> - <TableHead>Method</TableHead> - <TableHead className="text-right">Amount</TableHead> - </TableRow> - </TableHeader> - <TableBody> - <TableRow> - <TableCell>INV001</TableCell> - <TableCell>Paid</TableCell> - <TableCell>Credit Card</TableCell> - <TableCell className="text-right">$250.00</TableCell> - </TableRow> - <TableRow> - <TableCell>INV002</TableCell> - <TableCell>Pending</TableCell> - <TableCell>PayPal</TableCell> - <TableCell className="text-right">$150.00</TableCell> - </TableRow> - </TableBody> - <TableFooter> - <TableRow> - <TableCell colSpan={3}>Total</TableCell> - <TableCell className="text-right">$400.00</TableCell> - </TableRow> - </TableFooter> - </Table> - ), -}; - -export const WithoutFooter: Story = { - render: () => ( - <Table> - <TableHeader> - <TableRow> - <TableHead>Name</TableHead> - <TableHead>Email</TableHead> - <TableHead>Role</TableHead> - </TableRow> - </TableHeader> - <TableBody> - <TableRow> - <TableCell>Alice Johnson</TableCell> - <TableCell>alice@example.com</TableCell> - <TableCell>Admin</TableCell> - </TableRow> - <TableRow> - <TableCell>Bob Smith</TableCell> - <TableCell>bob@example.com</TableCell> - <TableCell>User</TableCell> - </TableRow> - </TableBody> - </Table> - ), -}; - -export const WithCustomStyles: Story = { - render: () => ( - <Table className="border-2 border-primary"> - <TableHeader> - <TableRow> - <TableHead className="bg-primary text-primary-foreground"> - Column 1 - </TableHead> - <TableHead className="bg-primary text-primary-foreground"> - Column 2 - </TableHead> - </TableRow> - </TableHeader> - <TableBody> - <TableRow> - <TableCell>Value 1</TableCell> - <TableCell>Value 2</TableCell> - </TableRow> - <TableRow> - <TableCell>Value 3</TableCell> - <TableCell>Value 4</TableCell> - </TableRow> - </TableBody> - </Table> - ), -}; diff --git a/autogpt_platform/frontend/src/components/ui/textarea.stories.tsx b/autogpt_platform/frontend/src/components/ui/textarea.stories.tsx deleted file mode 100644 index a2a3c28da3..0000000000 --- a/autogpt_platform/frontend/src/components/ui/textarea.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Textarea } from "./textarea"; - -const meta = { - title: "UI/Textarea", - component: Textarea, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - placeholder: { control: "text" }, - disabled: { control: "boolean" }, - }, -} satisfies Meta<typeof Textarea>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - placeholder: "Type your message here.", - }, -}; - -export const Disabled: Story = { - args: { - placeholder: "This textarea is disabled", - disabled: true, - }, -}; - -export const WithValue: Story = { - args: { - value: "This is some pre-filled text in the textarea.", - }, -}; - -export const CustomSized: Story = { - args: { - placeholder: "Custom sized textarea", - className: "w-[300px] h-[150px]", - }, -}; - -export const WithLabel: Story = { - render: (args) => ( - <div className="space-y-2"> - <label htmlFor="message" className="text-sm font-medium"> - Your message - </label> - <Textarea id="message" {...args} /> - </div> - ), - args: { - placeholder: "Type your message here.", - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/textarea.tsx b/autogpt_platform/frontend/src/components/ui/textarea.tsx index 997588b31d..6daf0f7ba0 100644 --- a/autogpt_platform/frontend/src/components/ui/textarea.tsx +++ b/autogpt_platform/frontend/src/components/ui/textarea.tsx @@ -2,8 +2,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; -export interface TextareaProps - extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} +export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>; const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( ({ className, ...props }, ref) => { diff --git a/autogpt_platform/frontend/src/components/ui/toast.stories.tsx b/autogpt_platform/frontend/src/components/ui/toast.stories.tsx deleted file mode 100644 index 101c83247c..0000000000 --- a/autogpt_platform/frontend/src/components/ui/toast.stories.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { - Toast, - ToastProvider, - ToastViewport, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, -} from "./toast"; - -const meta = { - title: "UI/Toast", - component: Toast, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - argTypes: { - variant: { - control: "select", - options: ["default", "destructive"], - }, - }, - decorators: [ - (Story) => ( - <ToastProvider> - <Story /> - <ToastViewport /> - </ToastProvider> - ), - ], -} satisfies Meta<typeof Toast>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: ( - <> - <ToastTitle>Default Toast</ToastTitle> - <ToastDescription>This is a default toast message.</ToastDescription> - </> - ), - }, -}; - -export const Destructive: Story = { - args: { - variant: "destructive", - children: ( - <> - <ToastTitle>Destructive Toast</ToastTitle> - <ToastDescription> - This is a destructive toast message. - </ToastDescription> - </> - ), - }, -}; - -export const WithAction: Story = { - args: { - children: ( - <> - <ToastTitle>Toast with Action</ToastTitle> - <ToastDescription>This toast has an action button.</ToastDescription> - <ToastAction altText="Try again">Try again</ToastAction> - </> - ), - }, -}; - -export const WithClose: Story = { - args: { - children: ( - <> - <ToastTitle>Closable Toast</ToastTitle> - <ToastDescription>This toast can be closed.</ToastDescription> - <ToastClose /> - </> - ), - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <> - <div className="flex items-center"> - <span className="mr-2">🎉</span> - <ToastTitle>Custom Toast</ToastTitle> - </div> - <ToastDescription>This toast has custom content.</ToastDescription> - </> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/toaster.stories.tsx b/autogpt_platform/frontend/src/components/ui/toaster.stories.tsx deleted file mode 100644 index e756f02998..0000000000 --- a/autogpt_platform/frontend/src/components/ui/toaster.stories.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { Toaster } from "./toaster"; -import { useToast } from "./use-toast"; -import { Button } from "./button"; - -const meta = { - title: "UI/Toaster", - component: Toaster, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta<typeof Toaster>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const ToasterDemo = () => { - const { toast } = useToast(); - - return ( - <div> - <Button - onClick={() => - toast({ - title: "Toast Title", - description: "This is a toast description", - }) - } - > - Show Toast - </Button> - <Toaster /> - </div> - ); -}; - -export const Default: Story = { - render: () => <ToasterDemo />, -}; - -export const WithTitle: Story = { - render: () => { - const { toast } = useToast(); - return ( - <div> - <Button - onClick={() => - toast({ - title: "Toast with Title", - }) - } - > - Show Toast with Title - </Button> - <Toaster /> - </div> - ); - }, -}; - -export const WithDescription: Story = { - render: () => { - const { toast } = useToast(); - return ( - <div> - <Button - onClick={() => - toast({ - description: "This is a toast with only a description", - }) - } - > - Show Toast with Description - </Button> - <Toaster /> - </div> - ); - }, -}; - -export const WithAction: Story = { - render: () => { - const { toast } = useToast(); - return ( - <div> - <Button - onClick={() => - toast({ - title: "Toast with Action", - description: "This toast has an action button.", - action: <Button variant="outline">Action</Button>, - }) - } - > - Show Toast with Action - </Button> - <Toaster /> - </div> - ); - }, -}; - -export const Destructive: Story = { - render: () => { - const { toast } = useToast(); - return ( - <div> - <Button - onClick={() => - toast({ - variant: "destructive", - title: "Destructive Toast", - description: "This is a destructive toast message.", - }) - } - > - Show Destructive Toast - </Button> - <Toaster /> - </div> - ); - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/tooltip.stories.tsx b/autogpt_platform/frontend/src/components/ui/tooltip.stories.tsx deleted file mode 100644 index 41835c6c51..0000000000 --- a/autogpt_platform/frontend/src/components/ui/tooltip.stories.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "./tooltip"; - -const meta = { - title: "UI/Tooltip", - component: Tooltip, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - decorators: [ - (Story) => ( - <TooltipProvider> - <Story /> - </TooltipProvider> - ), - ], - argTypes: { - children: { control: "text" }, - delayDuration: { control: "number" }, - }, -} satisfies Meta<typeof Tooltip>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - args: { - children: ( - <> - <TooltipTrigger asChild> - <Button variant="outline">Hover me</Button> - </TooltipTrigger> - <TooltipContent> - <p>This is a tooltip</p> - </TooltipContent> - </> - ), - }, -}; - -export const LongContent: Story = { - args: { - children: ( - <> - <TooltipTrigger asChild> - <Button variant="outline">Hover for long content</Button> - </TooltipTrigger> - <TooltipContent> - <p> - This is a tooltip with longer content that might wrap to multiple - lines. - </p> - </TooltipContent> - </> - ), - }, -}; - -export const CustomDelay: Story = { - args: { - delayDuration: 1000, - children: ( - <> - <TooltipTrigger asChild> - <Button variant="outline">Hover with delay</Button> - </TooltipTrigger> - <TooltipContent> - <p>This tooltip has a 1 second delay</p> - </TooltipContent> - </> - ), - }, -}; - -export const CustomContent: Story = { - args: { - children: ( - <> - <TooltipTrigger asChild> - <Button variant="outline">Hover for custom content</Button> - </TooltipTrigger> - <TooltipContent> - <div className="flex items-center"> - <span className="mr-2">🚀</span> - <p>Custom tooltip content</p> - </div> - </TooltipContent> - </> - ), - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/use-toast.stories.tsx b/autogpt_platform/frontend/src/components/ui/use-toast.stories.tsx deleted file mode 100644 index abdc24b113..0000000000 --- a/autogpt_platform/frontend/src/components/ui/use-toast.stories.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React from "react"; -import type { Meta, StoryObj } from "@storybook/react"; -import { Button } from "./button"; -import { useToast } from "./use-toast"; -import { Toaster } from "./toaster"; - -const meta = { - title: "UI/UseToast", - component: () => null, // UseToast is a hook, not a component - parameters: { - layout: "centered", - }, - tags: ["autodocs"], - decorators: [ - (Story) => ( - <> - <Story /> - <Toaster /> - </> - ), - ], -} satisfies Meta<typeof useToast>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const Default: Story = { - render: () => { - const { toast } = useToast(); - return ( - <Button onClick={() => toast({ title: "Default Toast" })}> - Show Default Toast - </Button> - ); - }, -}; - -export const WithDescription: Story = { - render: () => { - const { toast } = useToast(); - return ( - <Button - onClick={() => - toast({ - title: "Toast with Description", - description: "This is a more detailed toast message.", - }) - } - > - Show Toast with Description - </Button> - ); - }, -}; - -export const Destructive: Story = { - render: () => { - const { toast } = useToast(); - return ( - <Button - onClick={() => - toast({ - variant: "destructive", - title: "Destructive Toast", - description: "This action cannot be undone.", - }) - } - > - Show Destructive Toast - </Button> - ); - }, -}; - -export const WithAction: Story = { - render: () => { - const { toast } = useToast(); - return ( - <Button - onClick={() => - toast({ - title: "Toast with Action", - description: "Click the action button to do something.", - action: <Button variant="outline">Action</Button>, - }) - } - > - Show Toast with Action - </Button> - ); - }, -}; - -export const CustomDuration: Story = { - render: () => { - const { toast } = useToast(); - return ( - <Button - onClick={() => - toast({ - title: "Custom Duration Toast", - description: "This toast will disappear after 5 seconds.", - duration: 5000, - }) - } - > - Show Custom Duration Toast - </Button> - ); - }, -}; diff --git a/autogpt_platform/frontend/src/components/ui/use-toast.tsx b/autogpt_platform/frontend/src/components/ui/use-toast.tsx index dc758a8569..8903aec12f 100644 --- a/autogpt_platform/frontend/src/components/ui/use-toast.tsx +++ b/autogpt_platform/frontend/src/components/ui/use-toast.tsx @@ -16,12 +16,12 @@ type ToasterToast = ToastProps & { dismissable?: boolean; }; -const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; +type ActionTypes = { + ADD_TOAST: "ADD_TOAST"; + UPDATE_TOAST: "UPDATE_TOAST"; + DISMISS_TOAST: "DISMISS_TOAST"; + REMOVE_TOAST: "REMOVE_TOAST"; +}; let count = 0; @@ -30,7 +30,7 @@ function genId() { return count.toString(); } -type ActionType = typeof actionTypes; +type ActionType = ActionTypes; type Action = | { diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx index 94df1f5b2f..55b7c4d2bd 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx @@ -1,5 +1,8 @@ import { CustomEdge } from "@/components/CustomEdge"; import { CustomNode } from "@/components/CustomNode"; +import { useOnboarding } from "@/components/onboarding/onboarding-provider"; +import { InputItem } from "@/components/RunnerUIWrapper"; +import { useToast } from "@/components/ui/use-toast"; import BackendAPI, { Block, BlockIOSubSchema, @@ -8,6 +11,7 @@ import BackendAPI, { Graph, GraphExecutionID, GraphID, + GraphMeta, NodeExecutionResult, SpecialBlockID, } from "@/lib/autogpt-server-api"; @@ -19,13 +23,9 @@ import { } from "@/lib/utils"; import { MarkerType } from "@xyflow/react"; import Ajv from "ajv"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useRouter, useSearchParams, usePathname } from "next/navigation"; -import { useToast } from "@/components/ui/use-toast"; -import { InputItem } from "@/components/RunnerUIWrapper"; -import { GraphMeta } from "@/lib/autogpt-server-api"; import { default as NextLink } from "next/link"; -import { useOnboarding } from "@/components/onboarding/onboarding-provider"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; const ajv = new Ajv({ strict: false, allErrors: true }); @@ -279,9 +279,6 @@ export default function useAgentGraph( const cleanupSourceName = (sourceName: string) => isToolSourceName(sourceName) ? "tools" : sourceName; - const getToolArgName = (sourceName: string) => - isToolSourceName(sourceName) ? sourceName.split("_~_")[1] : null; - const getToolFuncName = (nodeId: string) => { const sinkNode = nodes.find((node) => node.id === nodeId); const sinkNodeName = sinkNode @@ -312,7 +309,7 @@ export default function useAgentGraph( new Map<string, NodeExecutionResult["status"]>(); // Update execution status for input edges - for (let key in executionData.input_data) { + for (const key in executionData.input_data) { if ( edge.target !== getFrontendId(executionData.node_id, nodes) || edge.targetHandle !== key @@ -328,7 +325,7 @@ export default function useAgentGraph( let beadUp = 0; let beadDown = 0; - execStatus.forEach((status) => { + execStatus.forEach((status: NodeExecutionResult["status"]) => { beadUp++; if (status !== "INCOMPLETE") { // Count any non-incomplete execution as consumed @@ -831,7 +828,7 @@ export default function useAgentGraph( return inputData; }; - let inputData = getNestedData(blockSchema, node.data.hardcodedValues); + const inputData = getNestedData(blockSchema, node.data.hardcodedValues); console.debug( `Final prepared input for ${node.data.blockType} (${node.id}):`, @@ -904,7 +901,6 @@ export default function useAgentGraph( }); const payload = { - id: savedAgent?.id!, name: agentName || `New Agent ${new Date().toISOString()}`, description: agentDescription || "", nodes: formattedNodes, @@ -914,11 +910,15 @@ export default function useAgentGraph( // To avoid saving the same graph, we compare the payload with the saved agent. // Differences in IDs are ignored. const comparedPayload = { - ...(({ id, ...rest }) => rest)(payload), + name: payload.name, + description: payload.description, nodes: payload.nodes.map( - ({ id, data, input_nodes, output_nodes, ...rest }) => rest, + ({ id: _, data: __, input_nodes: ___, output_nodes: ____, ...rest }) => + rest, + ), + links: payload.links.map( + ({ source_id: _, sink_id: __, ...rest }) => rest, ), - links: payload.links.map(({ source_id, sink_id, ...rest }) => rest), }; const comparedSavedAgent = { name: savedAgent?.name, @@ -947,7 +947,10 @@ export default function useAgentGraph( setNodesSyncedWithSavedAgent(false); newSavedAgent = savedAgent - ? await api.updateGraph(savedAgent.id, payload) + ? await api.updateGraph(savedAgent.id, { + ...payload, + id: savedAgent.id, + }) : await api.createGraph(payload); console.debug("Response from the API:", newSavedAgent); @@ -996,7 +999,7 @@ export default function useAgentGraph( ...edge, data: { ...edge.data, - edgeColor: edge.data?.edgeColor!, + edgeColor: edge.data?.edgeColor ?? "grey", beadUp: 0, beadDown: 0, }, diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 1be9967957..f82ed8e302 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -123,7 +123,7 @@ export default class BackendAPI { getUserCredit(): Promise<{ credits: number }> { try { return this._get("/credits"); - } catch (error) { + } catch { return Promise.resolve({ credits: 0 }); } } @@ -223,7 +223,7 @@ export default class BackendAPI { version?: number, for_export?: boolean, ): Promise<Graph> { - let query: Record<string, any> = {}; + const query: Record<string, any> = {}; if (version !== undefined) { query["version"] = version; } @@ -240,7 +240,7 @@ export default class BackendAPI { } createGraph(graph: GraphCreatable): Promise<Graph> { - let requestBody = { graph } as GraphCreateRequestBody; + const requestBody = { graph } as GraphCreateRequestBody; return this._request("POST", "/graphs", requestBody); } @@ -872,7 +872,7 @@ export default class BackendAPI { } else { errorDetail = errorData.detail || response.statusText; } - } catch (e) { + } catch { errorDetail = response.statusText; } diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index 873a6c735f..b4ba851053 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -630,6 +630,7 @@ export type StoreAgent = { description: string; runs: number; rating: number; + updated_at: string; }; export type StoreAgentsResponse = { diff --git a/autogpt_platform/frontend/src/lib/react-query/queryClient.ts b/autogpt_platform/frontend/src/lib/react-query/queryClient.ts new file mode 100644 index 0000000000..38bf858d21 --- /dev/null +++ b/autogpt_platform/frontend/src/lib/react-query/queryClient.ts @@ -0,0 +1,35 @@ +"use client"; + +import { isServer, QueryClient } from "@tanstack/react-query"; + +function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + // Added this because if staleTime is 0 (default), the data fetched on the server becomes stale immediately on the client, and it refetches again. + staleTime: 60 * 1000, + + // Highlighting some important defaults to avoid confusion + // Queries are stale by default → triggers background refetch + // Refetch triggers: on mount, window focus, reconnect + // Failed queries retry 3 times with exponential backoff + // Inactive queries are GC'd after 5 mins (gcTime = 5 * 60 * 1000) + // Structural sharing is enabled for efficient data comparison + // For more info, visit https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults + }, + }, + }); +} + +let browserQueryClient: QueryClient | undefined = undefined; + +export function getQueryClient() { + if (isServer) { + // Server: create new client every time (so one user's data doesn't leak to another) + return makeQueryClient(); + } else { + // Client: reuse the same client (to keep cache + avoid bugs if React suspends) + if (!browserQueryClient) browserQueryClient = makeQueryClient(); + return browserQueryClient; + } +} diff --git a/autogpt_platform/frontend/src/lib/supabase/getSupabaseClient.ts b/autogpt_platform/frontend/src/lib/supabase/getSupabaseClient.ts new file mode 100644 index 0000000000..055e31f394 --- /dev/null +++ b/autogpt_platform/frontend/src/lib/supabase/getSupabaseClient.ts @@ -0,0 +1,14 @@ +import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase"; +import { createBrowserClient } from "@supabase/ssr"; + +const isClient = typeof window !== "undefined"; + +export const getSupabaseClient = async () => { + return isClient + ? createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { isSingleton: true }, + ) + : await getServerSupabase(); +}; diff --git a/autogpt_platform/frontend/src/lib/supabase/hooks/useSupabase.ts b/autogpt_platform/frontend/src/lib/supabase/hooks/useSupabase.ts index df79d18373..55cfab6544 100644 --- a/autogpt_platform/frontend/src/lib/supabase/hooks/useSupabase.ts +++ b/autogpt_platform/frontend/src/lib/supabase/hooks/useSupabase.ts @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState, useRef } from "react"; import { createBrowserClient } from "@supabase/ssr"; -import { SignOut, User } from "@supabase/supabase-js"; +import { User } from "@supabase/supabase-js"; import { useRouter } from "next/navigation"; import { broadcastLogout, @@ -29,7 +29,7 @@ export function useSupabase() { } }, []); - async function logOut(options?: SignOut) { + async function logOut() { if (!supabase) return; broadcastLogout(); @@ -121,7 +121,7 @@ export function useSupabase() { const { data: { subscription }, - } = supabase.auth.onAuthStateChange((event, session) => { + } = supabase.auth.onAuthStateChange((_, session) => { const newUser = session?.user ?? null; // Only update if user actually changed to prevent unnecessary re-renders diff --git a/autogpt_platform/frontend/src/lib/supabase/middleware.ts b/autogpt_platform/frontend/src/lib/supabase/middleware.ts index da9ee26b20..0710daea71 100644 --- a/autogpt_platform/frontend/src/lib/supabase/middleware.ts +++ b/autogpt_platform/frontend/src/lib/supabase/middleware.ts @@ -1,11 +1,6 @@ import { createServerClient } from "@supabase/ssr"; import { NextResponse, type NextRequest } from "next/server"; -import { - PROTECTED_PAGES, - ADMIN_PAGES, - isProtectedPage, - isAdminPage, -} from "./helpers"; +import { isAdminPage, isProtectedPage } from "./helpers"; export async function updateSession(request: NextRequest) { let supabaseResponse = NextResponse.next({ @@ -31,7 +26,7 @@ export async function updateSession(request: NextRequest) { return request.cookies.getAll(); }, setAll(cookiesToSet) { - cookiesToSet.forEach(({ name, value, options }) => + cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value), ); supabaseResponse = NextResponse.next({ @@ -51,7 +46,6 @@ export async function updateSession(request: NextRequest) { const { data: { user }, - error, } = await supabase.auth.getUser(); const userRole = user?.role; diff --git a/autogpt_platform/frontend/src/lib/supabase/server/getServerSupabase.ts b/autogpt_platform/frontend/src/lib/supabase/server/getServerSupabase.ts index 555d32982c..da85a5195a 100644 --- a/autogpt_platform/frontend/src/lib/supabase/server/getServerSupabase.ts +++ b/autogpt_platform/frontend/src/lib/supabase/server/getServerSupabase.ts @@ -1,8 +1,10 @@ -import type { UnsafeUnwrappedCookies } from "next/headers"; -import { createServerClient } from "@supabase/ssr"; +import { createServerClient, type CookieOptions } from "@supabase/ssr"; + +type Cookies = { name: string; value: string; options?: CookieOptions }[]; export async function getServerSupabase() { // Need require here, so Next.js doesn't complain about importing this on client side + // eslint-disable-next-line @typescript-eslint/no-require-imports const { cookies } = require("next/headers"); const cookieStore = await cookies(); @@ -15,7 +17,7 @@ export async function getServerSupabase() { getAll() { return cookieStore.getAll(); }, - setAll(cookiesToSet) { + setAll(cookiesToSet: Cookies) { try { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options), diff --git a/autogpt_platform/frontend/src/lib/supabase/server/getServerUser.ts b/autogpt_platform/frontend/src/lib/supabase/server/getServerUser.ts index 2b8d57e2a0..c8fc3e2b22 100644 --- a/autogpt_platform/frontend/src/lib/supabase/server/getServerUser.ts +++ b/autogpt_platform/frontend/src/lib/supabase/server/getServerUser.ts @@ -10,7 +10,6 @@ export async function getServerUser() { try { const { data: { user }, - error: _, } = await supabase.auth.getUser(); // if (error) { // // FIX: Suppressing error for now. Need to stop the nav bar calling this all the time diff --git a/autogpt_platform/frontend/src/lib/utils.ts b/autogpt_platform/frontend/src/lib/utils.ts index 0d3e1773ce..3eea21add8 100644 --- a/autogpt_platform/frontend/src/lib/utils.ts +++ b/autogpt_platform/frontend/src/lib/utils.ts @@ -1,7 +1,7 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; -import { Category, Graph } from "@/lib/autogpt-server-api/types"; +import { Category } from "@/lib/autogpt-server-api/types"; import { NodeDimension } from "@/components/Flow"; export function cn(...inputs: ClassValue[]) { @@ -396,3 +396,8 @@ export function getValue(key: string, value: any) { export function isEmptyOrWhitespace(str: string | undefined | null): boolean { return !str || str.trim().length === 0; } + +/** Check if a value is an object or not */ +export function isObject(value: unknown): value is Record<string, unknown> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} diff --git a/autogpt_platform/frontend/src/stories/Button.stories.ts b/autogpt_platform/frontend/src/stories/Button.stories.ts deleted file mode 100644 index 83765401bc..0000000000 --- a/autogpt_platform/frontend/src/stories/Button.stories.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { fn } from "@storybook/test"; - -import { Button } from "./Button"; - -// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export -const meta = { - title: "Example/Button", - component: Button, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - layout: "centered", - }, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ["autodocs"], - // More on argTypes: https://storybook.js.org/docs/api/argtypes - argTypes: { - backgroundColor: { control: "color" }, - }, - // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args - args: { onClick: fn() }, -} satisfies Meta<typeof Button>; - -export default meta; -type Story = StoryObj<typeof meta>; - -// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args -export const Primary: Story = { - args: { - primary: true, - label: "Button", - }, -}; - -export const Secondary: Story = { - args: { - label: "Button", - }, -}; - -export const Large: Story = { - args: { - size: "large", - label: "Button", - }, -}; - -export const Small: Story = { - args: { - size: "small", - label: "Button", - }, -}; - -export const NotPrimaryButton: Story = { - args: { - primary: false, - label: "Button", - }, -}; diff --git a/autogpt_platform/frontend/src/stories/Button.tsx b/autogpt_platform/frontend/src/stories/Button.tsx deleted file mode 100644 index 8293848524..0000000000 --- a/autogpt_platform/frontend/src/stories/Button.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; - -import "./button.css"; - -export interface ButtonProps { - /** Is this the principal call to action on the page? */ - primary?: boolean; - /** What background color to use */ - backgroundColor?: string; - /** How large should the button be? */ - size?: "small" | "medium" | "large"; - /** Button contents */ - label: string; - /** Optional click handler */ - onClick?: () => void; -} - -/** Primary UI component for user interaction */ -export const Button = ({ - primary = false, - size = "medium", - backgroundColor, - label, - ...props -}: ButtonProps) => { - const mode = primary - ? "storybook-button--primary" - : "storybook-button--secondary"; - return ( - <button - type="button" - className={["storybook-button", `storybook-button--${size}`, mode].join( - " ", - )} - {...props} - > - {label} - <style jsx>{` - button { - background-color: ${backgroundColor}; - } - `}</style> - </button> - ); -}; diff --git a/autogpt_platform/frontend/src/stories/Header.stories.ts b/autogpt_platform/frontend/src/stories/Header.stories.ts deleted file mode 100644 index eb3e27757b..0000000000 --- a/autogpt_platform/frontend/src/stories/Header.stories.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { fn } from "@storybook/test"; - -import { Header } from "./Header"; - -const meta = { - title: "Example/Header", - component: Header, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ["autodocs"], - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout - layout: "fullscreen", - }, - args: { - onLogin: fn(), - onLogout: fn(), - onCreateAccount: fn(), - }, -} satisfies Meta<typeof Header>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const LoggedIn: Story = { - args: { - user: { - name: "Jane Doe", - }, - }, -}; - -export const LoggedOut: Story = {}; diff --git a/autogpt_platform/frontend/src/stories/Header.tsx b/autogpt_platform/frontend/src/stories/Header.tsx deleted file mode 100644 index 9b143daf98..0000000000 --- a/autogpt_platform/frontend/src/stories/Header.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from "react"; - -import { Button } from "./Button"; -import "./header.css"; - -type User = { - name: string; -}; - -export interface HeaderProps { - user?: User; - onLogin?: () => void; - onLogout?: () => void; - onCreateAccount?: () => void; -} - -export const Header = ({ - user, - onLogin, - onLogout, - onCreateAccount, -}: HeaderProps) => ( - <header> - <div className="storybook-header"> - <div> - <svg - width="32" - height="32" - viewBox="0 0 32 32" - xmlns="http://www.w3.org/2000/svg" - > - <g fill="none" fillRule="evenodd"> - <path - d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z" - fill="#FFF" - /> - <path - d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z" - fill="#555AB9" - /> - <path - d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z" - fill="#91BAF8" - /> - </g> - </svg> - <h1>Acme</h1> - </div> - <div> - {user ? ( - <> - <span className="welcome"> - Welcome, <b>{user.name}</b>! - </span> - <Button size="small" onClick={onLogout} label="Log out" /> - </> - ) : ( - <> - <Button size="small" onClick={onLogin} label="Log in" /> - <Button - primary - size="small" - onClick={onCreateAccount} - label="Sign up" - /> - </> - )} - </div> - </div> - </header> -); diff --git a/autogpt_platform/frontend/src/stories/Page.stories.ts b/autogpt_platform/frontend/src/stories/Page.stories.ts deleted file mode 100644 index f5c5a0b38f..0000000000 --- a/autogpt_platform/frontend/src/stories/Page.stories.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { expect, userEvent, within } from "@storybook/test"; - -import { Page } from "./Page"; - -const meta = { - title: "Example/Page", - component: Page, - parameters: { - // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout - layout: "fullscreen", - }, -} satisfies Meta<typeof Page>; - -export default meta; -type Story = StoryObj<typeof meta>; - -export const LoggedOut: Story = {}; - -// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testing -export const LoggedIn: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const loginButton = canvas.getByRole("button", { name: /Log in/i }); - await expect(loginButton).toBeInTheDocument(); - await userEvent.click(loginButton); - await expect(loginButton).not.toBeInTheDocument(); - - const logoutButton = canvas.getByRole("button", { name: /Log out/i }); - await expect(logoutButton).toBeInTheDocument(); - }, -}; diff --git a/autogpt_platform/frontend/src/stories/Page.tsx b/autogpt_platform/frontend/src/stories/Page.tsx deleted file mode 100644 index a294b193b8..0000000000 --- a/autogpt_platform/frontend/src/stories/Page.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from "react"; - -import { Header } from "./Header"; -import "./page.css"; - -type User = { - name: string; -}; - -export const Page: React.FC = () => { - const [user, setUser] = React.useState<User>(); - - return ( - <article> - <Header - user={user} - onLogin={() => setUser({ name: "Jane Doe" })} - onLogout={() => setUser(undefined)} - onCreateAccount={() => setUser({ name: "Jane Doe" })} - /> - - <section className="storybook-page"> - <h2>Pages in Storybook</h2> - <p> - We recommend building UIs with a{" "} - <a - href="https://componentdriven.org" - target="_blank" - rel="noopener noreferrer" - > - <strong>component-driven</strong> - </a>{" "} - process starting with atomic components and ending with pages. - </p> - <p> - Render pages with mock data. This makes it easy to build and review - page states without needing to navigate to them in your app. Here are - some handy patterns for managing page data in Storybook: - </p> - <ul> - <li> - Use a higher-level connected component. Storybook helps you compose - such data from the "args" of child component stories - </li> - <li> - Assemble data in the page component from your services. You can mock - these services out using Storybook. - </li> - </ul> - <p> - Get a guided tutorial on component-driven development at{" "} - <a - href="https://storybook.js.org/tutorials/" - target="_blank" - rel="noopener noreferrer" - > - Storybook tutorials - </a> - . Read more in the{" "} - <a - href="https://storybook.js.org/docs" - target="_blank" - rel="noopener noreferrer" - > - docs - </a> - . - </p> - <div className="tip-wrapper"> - <span className="tip">Tip</span> Adjust the width of the canvas with - the{" "} - <svg - width="10" - height="10" - viewBox="0 0 12 12" - xmlns="http://www.w3.org/2000/svg" - > - <g fill="none" fillRule="evenodd"> - <path - d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z" - id="a" - fill="#999" - /> - </g> - </svg> - Viewports addon in the toolbar - </div> - </section> - </article> - ); -}; diff --git a/autogpt_platform/frontend/src/stories/button.css b/autogpt_platform/frontend/src/stories/button.css deleted file mode 100644 index 4d951196a9..0000000000 --- a/autogpt_platform/frontend/src/stories/button.css +++ /dev/null @@ -1,30 +0,0 @@ -.storybook-button { - display: inline-block; - cursor: pointer; - border: 0; - border-radius: 3em; - font-weight: 700; - line-height: 1; - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; -} -.storybook-button--primary { - background-color: #1ea7fd; - color: white; -} -.storybook-button--secondary { - box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; - background-color: transparent; - color: #333; -} -.storybook-button--small { - padding: 10px 16px; - font-size: 12px; -} -.storybook-button--medium { - padding: 11px 20px; - font-size: 14px; -} -.storybook-button--large { - padding: 12px 24px; - font-size: 16px; -} diff --git a/autogpt_platform/frontend/src/stories/header.css b/autogpt_platform/frontend/src/stories/header.css deleted file mode 100644 index ad77492780..0000000000 --- a/autogpt_platform/frontend/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.storybook-header { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -.storybook-header svg { - display: inline-block; - vertical-align: top; -} - -.storybook-header h1 { - display: inline-block; - vertical-align: top; - margin: 6px 0 6px 10px; - font-weight: 700; - font-size: 20px; - line-height: 1; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - margin-right: 10px; - color: #333; - font-size: 14px; -} diff --git a/autogpt_platform/frontend/src/stories/page.css b/autogpt_platform/frontend/src/stories/page.css deleted file mode 100644 index d1cf49597a..0000000000 --- a/autogpt_platform/frontend/src/stories/page.css +++ /dev/null @@ -1,69 +0,0 @@ -.storybook-page { - margin: 0 auto; - padding: 48px 20px; - max-width: 600px; - color: #333; - font-size: 14px; - line-height: 24px; - font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -.storybook-page h2 { - display: inline-block; - vertical-align: top; - margin: 0 0 4px; - font-weight: 700; - font-size: 32px; - line-height: 1; -} - -.storybook-page p { - margin: 1em 0; -} - -.storybook-page a { - color: #1ea7fd; - text-decoration: none; -} - -.storybook-page ul { - margin: 1em 0; - padding-left: 30px; -} - -.storybook-page li { - margin-bottom: 8px; -} - -.storybook-page .tip { - display: inline-block; - vertical-align: top; - margin-right: 10px; - border-radius: 1em; - background: #e7fdd8; - padding: 4px 12px; - color: #66bf3c; - font-weight: 700; - font-size: 11px; - line-height: 12px; -} - -.storybook-page .tip-wrapper { - margin-top: 40px; - margin-bottom: 40px; - font-size: 13px; - line-height: 20px; -} - -.storybook-page .tip-wrapper svg { - display: inline-block; - vertical-align: top; - margin-top: 3px; - margin-right: 4px; - width: 12px; - height: 12px; -} - -.storybook-page .tip-wrapper svg path { - fill: #1ea7fd; -} diff --git a/autogpt_platform/frontend/src/tests/build.spec.ts b/autogpt_platform/frontend/src/tests/build.spec.ts index c6793c46e3..3c6a87ba94 100644 --- a/autogpt_platform/frontend/src/tests/build.spec.ts +++ b/autogpt_platform/frontend/src/tests/build.spec.ts @@ -13,7 +13,7 @@ test.describe("Build", () => { //(1)! // Reason Ignore: admonishment is in the wrong place visually with correct prettier rules // prettier-ignore - test.beforeEach(async ({ page, loginPage, testUser }, testInfo) => { //(3)! ts-ignore + test.beforeEach(async ({ page, loginPage, testUser }) => { //(3)! ts-ignore buildPage = new BuildPage(page); // Start each test with login using worker auth diff --git a/autogpt_platform/frontend/src/tests/fixtures/test-user.fixture.ts b/autogpt_platform/frontend/src/tests/fixtures/test-user.fixture.ts index 580121df6f..e2fc2a003e 100644 --- a/autogpt_platform/frontend/src/tests/fixtures/test-user.fixture.ts +++ b/autogpt_platform/frontend/src/tests/fixtures/test-user.fixture.ts @@ -1,42 +1,6 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { createClient, SupabaseClient } from "@supabase/supabase-js"; - export type TestUser = { email: string; password: string; id?: string; }; - -let supabase: SupabaseClient; - -function getSupabaseAdmin() { - if (!supabase) { - supabase = createClient( - process.env.SUPABASE_URL!, - process.env.SUPABASE_SERVICE_ROLE_KEY!, - { - auth: { - autoRefreshToken: false, - persistSession: false, - }, - }, - ); - } - return supabase; -} - -async function deleteTestUser(userId: string) { - const supabase = getSupabaseAdmin(); - - try { - const { error } = await supabase.auth.admin.deleteUser(userId); - - if (error) { - console.warn(`Warning: Failed to delete test user: ${error.message}`); - } - } catch (error) { - console.warn( - `Warning: Error during user cleanup: ${(error as Error).message}`, - ); - } -} diff --git a/autogpt_platform/frontend/src/tests/monitor.spec.ts b/autogpt_platform/frontend/src/tests/monitor.spec.ts index 3b55b2f6e3..a7e588acf0 100644 --- a/autogpt_platform/frontend/src/tests/monitor.spec.ts +++ b/autogpt_platform/frontend/src/tests/monitor.spec.ts @@ -1,4 +1,4 @@ -import { expect, TestInfo } from "@playwright/test"; +import { TestInfo } from "@playwright/test"; import { test } from "./fixtures"; import { BuildPage } from "./pages/build.page"; import { MonitorPage } from "./pages/monitor.page"; @@ -48,7 +48,7 @@ test.describe("Monitor", () => { }); }); - test("user can view agents", async ({ page }) => { + test("user can view agents", async () => { const agents = await monitorPage.listAgents(); // there should be at least one agent await test.expect(agents.length).toBeGreaterThan(0); @@ -117,7 +117,7 @@ test.describe("Monitor", () => { await test.expect(importedAgent).toBeDefined(); }); - test("user can view runs", async ({ page }) => { + test("user can view runs", async () => { const runs = await monitorPage.listRuns(); console.log(runs); // there should be at least one run diff --git a/autogpt_platform/frontend/src/tests/pages/build.page.ts b/autogpt_platform/frontend/src/tests/pages/build.page.ts index 023bfc0ebb..8fcf97dad8 100644 --- a/autogpt_platform/frontend/src/tests/pages/build.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/build.page.ts @@ -1,4 +1,4 @@ -import { ElementHandle, Locator, Page } from "@playwright/test"; +import { Locator, Page } from "@playwright/test"; import { BasePage } from "./base.page"; export interface Block { @@ -136,7 +136,7 @@ export class BuildPage extends BasePage { } } - async getBlockOutputs(blockId: string): Promise<string[]> { + async getBlockOutputs(): Promise<string[]> { throw new Error("Not implemented"); // try { // const node = await this.page @@ -151,7 +151,7 @@ export class BuildPage extends BasePage { } async _buildBlockSelector(blockId: string, dataId?: string): Promise<string> { - let selector = dataId + const selector = dataId ? `[data-id="${dataId}"] [data-blockid="${blockId}"]` : `[data-blockid="${blockId}"]`; return selector; @@ -283,7 +283,7 @@ export class BuildPage extends BasePage { try { await this.page.waitForLoadState("domcontentloaded", { timeout: 10_000 }); return true; - } catch (error) { + } catch { return false; } } @@ -432,8 +432,8 @@ export class BuildPage extends BasePage { } // Parse current coordinates - let currentX = parseFloat(matches[1]); - let currentY = parseFloat(matches[2]); + const currentX = parseFloat(matches[1]); + const currentY = parseFloat(matches[2]); // Calculate new position let newX = currentX; diff --git a/autogpt_platform/frontend/src/tests/pages/monitor.page.ts b/autogpt_platform/frontend/src/tests/pages/monitor.page.ts index 1679c5397f..74aebe1000 100644 --- a/autogpt_platform/frontend/src/tests/pages/monitor.page.ts +++ b/autogpt_platform/frontend/src/tests/pages/monitor.page.ts @@ -1,4 +1,4 @@ -import { ElementHandle, Locator, Page } from "@playwright/test"; +import { Page } from "@playwright/test"; import { BasePage } from "./base.page"; import path from "path"; @@ -18,10 +18,6 @@ interface Run { status: string; } -interface AgentRun extends Agent { - runs: Run[]; -} - interface Schedule { id: string; graphName: string; @@ -72,7 +68,7 @@ export class MonitorPage extends BasePage { ]); return true; - } catch (error) { + } catch { return false; } } diff --git a/autogpt_platform/frontend/src/tests/profile.spec.ts b/autogpt_platform/frontend/src/tests/profile.spec.ts index 0f29b04556..c2f644c113 100644 --- a/autogpt_platform/frontend/src/tests/profile.spec.ts +++ b/autogpt_platform/frontend/src/tests/profile.spec.ts @@ -13,10 +13,7 @@ test.describe("Profile", () => { await test.expect(page).toHaveURL("/marketplace"); }); - test("user can view their profile information", async ({ - page, - testUser, - }) => { + test("user can view their profile information", async ({ page }) => { await profilePage.navbar.clickProfileLink(); // workaround for #8788 // sleep for 10 seconds to allow page to load due to bug in our system @@ -39,7 +36,7 @@ test.describe("Profile", () => { await test.expect(profilePage.isLoaded()).resolves.toBeTruthy(); }); - test("profile displays user Credential providers", async ({ page }) => { + test("profile displays user Credential providers", async () => { await profilePage.navbar.clickProfileLink(); // await test diff --git a/autogpt_platform/frontend/src/tests/util.spec.ts b/autogpt_platform/frontend/src/tests/util.spec.ts index 84fb9a3de6..ddf26b4e5c 100644 --- a/autogpt_platform/frontend/src/tests/util.spec.ts +++ b/autogpt_platform/frontend/src/tests/util.spec.ts @@ -99,6 +99,7 @@ test.describe("Nested Property Setter Tests", () => { }).toThrow("Invalid property name: __proto__"); // Verify no pollution occurred + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore expect({}.polluted).toBeUndefined(); }); diff --git a/autogpt_platform/frontend/tailwind.config.ts b/autogpt_platform/frontend/tailwind.config.ts index 9f2a706bcd..d0607b3412 100644 --- a/autogpt_platform/frontend/tailwind.config.ts +++ b/autogpt_platform/frontend/tailwind.config.ts @@ -1,4 +1,6 @@ import type { Config } from "tailwindcss"; +import tailwindcssAnimate from "tailwindcss-animate"; +import { colors } from "./src/components/styles/colors"; const config = { darkMode: ["class"], @@ -19,6 +21,13 @@ const config = { poppins: ["var(--font-poppins)"], }, colors: { + // *** APPROVED DESIGN SYSTEM COLORS *** + // These are the ONLY colors that should be used in our app + ...colors, + + // Legacy colors - DO NOT USE THESE IN NEW CODE + // These are kept only to prevent breaking existing styles + // Use the approved design system colors above instead border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", @@ -52,7 +61,6 @@ const config = { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, - // Extend the color palette with custom colors customGray: { 100: "#d9d9d9", 200: "#a8a8a8", @@ -141,7 +149,7 @@ const config = { }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [tailwindcssAnimate], } satisfies Config; export default config; diff --git a/docs/requirements.txt b/docs/requirements.txt index 6075801360..ea0eab8c2a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,3 +5,4 @@ pymdown-extensions mkdocs-git-revision-date-localized-plugin zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability +requests>=2.32.4 # not directly required, pinned by Snyk to avoid a vulnerability