Merge branch 'dev' of github.com:Significant-Gravitas/AutoGPT into zamilmajdy/fix-iteration-and-timer-block

This commit is contained in:
Zamil Majdy
2024-11-12 13:04:02 +07:00
63 changed files with 2064 additions and 1470 deletions

View File

@@ -1,36 +1,38 @@
### Background
<!-- Clearly explain the need for these changes: -->
### Changes 🏗️
<!-- Concisely describe all of the changes made in this pull request: -->
### Checklist 📋
### Testing 🔍
> [!NOTE]
Only for the new autogpt platform, currently in autogpt_platform/
#### For code changes:
- [ ] I have clearly listed my changes in the PR description
- [ ] I have made a test plan
- [ ] I have tested my changes according to the test plan:
<!-- Put your test plan here: -->
- [ ] ...
<!--
Please make sure your changes have been tested and are in good working condition.
Here is a list of our critical paths, if you need some inspiration on what and how to test:
-->
<details>
<summary>Example test plan</summary>
- [ ] Create from scratch and execute an agent with at least 3 blocks
- [ ] Import an agent from file upload, and confirm it executes correctly
- [ ] Upload agent to marketplace
- [ ] Import an agent from marketplace and confirm it executes correctly
- [ ] Edit an agent from monitor, and confirm it executes correctly
</details>
- Create from scratch and execute an agent with at least 3 blocks
- Import an agent from file upload, and confirm it executes correctly
- Upload agent to marketplace
- Import an agent from marketplace and confirm it executes correctly
- Edit an agent from monitor, and confirm it executes correctly
#### For configuration changes:
- [ ] `.env.example` is updated or already compatible with my changes
- [ ] `docker-compose.yml` is updated or already compatible with my changes
- [ ] I have included a list of my configuration changes in the PR description (under **Changes**)
### Configuration Changes 📝
> [!NOTE]
Only for the new autogpt platform, currently in autogpt_platform/
<details>
<summary>Examples of configuration changes</summary>
If you're making configuration or infrastructure changes, please remember to check you've updated the related infrastructure code in the autogpt_platform/infra folder.
Examples of such changes might include:
- Changing ports
- Adding new services that need to communicate with each other
- Secrets or environment variable changes
- New or infrastructure changes such as databases
- Changing ports
- Adding new services that need to communicate with each other
- Secrets or environment variable changes
- New or infrastructure changes such as databases
</details>

View File

@@ -1,4 +1,4 @@
name: AutoGPT Platform - Build, Push, and Deploy Prod Environment
name: AutoGPT Platform - Deploy Prod Environment
on:
release:
@@ -8,12 +8,6 @@ permissions:
contents: 'read'
id-token: 'write'
env:
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GKE_CLUSTER: prod-gke-cluster
GKE_ZONE: us-central1-a
NAMESPACE: prod-agpt
jobs:
migrate:
environment: production
@@ -48,135 +42,14 @@ jobs:
env:
DATABASE_URL: ${{ secrets.MARKET_DATABASE_URL }}
build-push-deploy:
environment: production
name: Build, Push, and Deploy
trigger:
needs: migrate
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/1021527134101/locations/global/workloadIdentityPools/prod-pool/providers/github'
service_account: 'prod-github-actions-sa@agpt-prod.iam.gserviceaccount.com'
token_format: 'access_token'
create_credentials_file: true
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v2'
- name: 'Configure Docker'
run: |
gcloud auth configure-docker us-east1-docker.pkg.dev
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Check for changes
id: check_changes
run: |
git fetch origin master
BACKEND_CHANGED=$(git diff --name-only origin/master HEAD | grep "^autogpt_platform/backend/" && echo "true" || echo "false")
FRONTEND_CHANGED=$(git diff --name-only origin/master HEAD | grep "^autogpt_platform/frontend/" && echo "true" || echo "false")
MARKET_CHANGED=$(git diff --name-only origin/master HEAD | grep "^autogpt_platform/market/" && echo "true" || echo "false")
echo "backend_changed=$BACKEND_CHANGED" >> $GITHUB_OUTPUT
echo "frontend_changed=$FRONTEND_CHANGED" >> $GITHUB_OUTPUT
echo "market_changed=$MARKET_CHANGED" >> $GITHUB_OUTPUT
- name: Get GKE credentials
uses: 'google-github-actions/get-gke-credentials@v2'
with:
cluster_name: ${{ env.GKE_CLUSTER }}
location: ${{ env.GKE_ZONE }}
- name: Build and Push Backend
if: steps.check_changes.outputs.backend_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/backend/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-prod/agpt-backend-prod/agpt-backend-prod:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Build and Push Frontend
if: steps.check_changes.outputs.frontend_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/frontend/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-prod/agpt-frontend-prod/agpt-frontend-prod:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Build and Push Market
if: steps.check_changes.outputs.market_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/market/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-prod/agpt-market-prod/agpt-market-prod:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.4.0
- name: Deploy Backend
if: steps.check_changes.outputs.backend_changed == 'true'
run: |
helm upgrade autogpt-server ./autogpt-server \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-server/values.yaml \
-f autogpt-server/values.prod.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Websocket
if: steps.check_changes.outputs.backend_changed == 'true'
run: |
helm upgrade autogpt-websocket-server ./autogpt-websocket-server \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-websocket-server/values.yaml \
-f autogpt-websocket-server/values.prod.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Market
if: steps.check_changes.outputs.market_changed == 'true'
run: |
helm upgrade autogpt-market ./autogpt-market \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-market/values.yaml \
-f autogpt-market/values.prod.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Frontend
if: steps.check_changes.outputs.frontend_changed == 'true'
run: |
helm upgrade autogpt-builder ./autogpt-builder \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-builder/values.yaml \
-f autogpt-builder/values.prod.yaml \
--set image.tag=${{ github.sha }}
- name: Trigger deploy workflow
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.DEPLOY_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: build_deploy_prod
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "repository": "${{ github.repository }}"}'

View File

@@ -0,0 +1,57 @@
name: AutoGPT Platform - Deploy Dev Environment
on:
push:
branches: [ dev ]
paths:
- 'autogpt_platform/**'
permissions:
contents: 'read'
id-token: 'write'
jobs:
migrate:
environment: develop
name: Run migrations for AutoGPT Platform
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install prisma
- name: Run Backend Migrations
working-directory: ./autogpt_platform/backend
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.BACKEND_DATABASE_URL }}
- name: Run Market Migrations
working-directory: ./autogpt_platform/market
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.MARKET_DATABASE_URL }}
trigger:
needs: migrate
runs-on: ubuntu-latest
steps:
- name: Trigger deploy workflow
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.DEPLOY_TOKEN }}
repository: Significant-Gravitas/AutoGPT_cloud_infrastructure
event-type: build_deploy_dev
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "repository": "${{ github.repository }}"}'

View File

@@ -1,186 +0,0 @@
name: AutoGPT Platform - Build, Push, and Deploy Dev Environment
on:
push:
branches: [ dev ]
paths:
- 'autogpt_platform/backend/**'
- 'autogpt_platform/frontend/**'
- 'autogpt_platform/market/**'
permissions:
contents: 'read'
id-token: 'write'
env:
PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
GKE_CLUSTER: dev-gke-cluster
GKE_ZONE: us-central1-a
NAMESPACE: dev-agpt
jobs:
migrate:
environment: develop
name: Run migrations for AutoGPT Platform
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install prisma
- name: Run Backend Migrations
working-directory: ./autogpt_platform/backend
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.BACKEND_DATABASE_URL }}
- name: Run Market Migrations
working-directory: ./autogpt_platform/market
run: |
python -m prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.MARKET_DATABASE_URL }}
build-push-deploy:
name: Build, Push, and Deploy
needs: migrate
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/638488734936/locations/global/workloadIdentityPools/dev-pool/providers/github'
service_account: 'dev-github-actions-sa@agpt-dev.iam.gserviceaccount.com'
token_format: 'access_token'
create_credentials_file: true
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v2'
- name: 'Configure Docker'
run: |
gcloud auth configure-docker us-east1-docker.pkg.dev
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Check for changes
id: check_changes
run: |
git fetch origin dev
BACKEND_CHANGED=$(git diff --name-only origin/dev HEAD | grep "^autogpt_platform/backend/" && echo "true" || echo "false")
FRONTEND_CHANGED=$(git diff --name-only origin/dev HEAD | grep "^autogpt_platform/frontend/" && echo "true" || echo "false")
MARKET_CHANGED=$(git diff --name-only origin/dev HEAD | grep "^autogpt_platform/market/" && echo "true" || echo "false")
echo "backend_changed=$BACKEND_CHANGED" >> $GITHUB_OUTPUT
echo "frontend_changed=$FRONTEND_CHANGED" >> $GITHUB_OUTPUT
echo "market_changed=$MARKET_CHANGED" >> $GITHUB_OUTPUT
- name: Get GKE credentials
uses: 'google-github-actions/get-gke-credentials@v2'
with:
cluster_name: ${{ env.GKE_CLUSTER }}
location: ${{ env.GKE_ZONE }}
- name: Build and Push Backend
if: steps.check_changes.outputs.backend_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/backend/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-dev/agpt-backend-dev/agpt-backend-dev:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Build and Push Frontend
if: steps.check_changes.outputs.frontend_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/frontend/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-dev/agpt-frontend-dev/agpt-frontend-dev:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Build and Push Market
if: steps.check_changes.outputs.market_changed == 'true'
uses: docker/build-push-action@v2
with:
context: .
file: ./autogpt_platform/market/Dockerfile
push: true
tags: us-east1-docker.pkg.dev/agpt-dev/agpt-market-dev/agpt-market-dev:${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.4.0
- name: Deploy Backend
if: steps.check_changes.outputs.backend_changed == 'true'
run: |
helm upgrade autogpt-server ./autogpt-server \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-server/values.yaml \
-f autogpt-server/values.dev.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Websocket
if: steps.check_changes.outputs.backend_changed == 'true'
run: |
helm upgrade autogpt-websocket-server ./autogpt-websocket-server \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-websocket-server/values.yaml \
-f autogpt-websocket-server/values.dev.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Market
if: steps.check_changes.outputs.market_changed == 'true'
run: |
helm upgrade autogpt-market ./autogpt-market \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-market/values.yaml \
-f autogpt-market/values.dev.yaml \
--set image.tag=${{ github.sha }}
- name: Deploy Frontend
if: steps.check_changes.outputs.frontend_changed == 'true'
run: |
helm upgrade autogpt-builder ./autogpt-builder \
--namespace ${{ env.NAMESPACE }} \
-f autogpt-builder/values.yaml \
-f autogpt-builder/values.dev.yaml \
--set image.tag=${{ github.sha }}

View File

@@ -9,7 +9,7 @@ repos:
- id: check-merge-conflict
- id: check-symlinks
- id: debug-statements
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
@@ -19,26 +19,116 @@ repos:
files: ^autogpt_platform/
stages: [push]
- repo: local
# For proper type checking, all dependencies need to be up-to-date.
# It's also a good idea to check that poetry.lock is consistent with pyproject.toml.
hooks:
- id: poetry-install
name: Check & Install dependencies - AutoGPT Platform - Backend
alias: poetry-install-platform-backend
entry: poetry -C autogpt_platform/backend install
# include autogpt_libs source (since it's a path dependency)
files: ^autogpt_platform/(backend|autogpt_libs)/poetry\.lock$
types: [file]
language: system
pass_filenames: false
- id: poetry-install
name: Check & Install dependencies - AutoGPT Platform - Libs
alias: poetry-install-platform-libs
entry: poetry -C autogpt_platform/autogpt_libs install
files: ^autogpt_platform/autogpt_libs/poetry\.lock$
types: [file]
language: system
pass_filenames: false
- id: poetry-install
name: Check & Install dependencies - Classic - AutoGPT
alias: poetry-install-classic-autogpt
entry: poetry -C classic/original_autogpt install
# include forge source (since it's a path dependency)
files: ^classic/(original_autogpt|forge)/poetry\.lock$
types: [file]
language: system
pass_filenames: false
- id: poetry-install
name: Check & Install dependencies - Classic - Forge
alias: poetry-install-classic-forge
entry: poetry -C classic/forge install
files: ^classic/forge/poetry\.lock$
types: [file]
language: system
pass_filenames: false
- id: poetry-install
name: Check & Install dependencies - Classic - Benchmark
alias: poetry-install-classic-benchmark
entry: poetry -C classic/benchmark install
files: ^classic/benchmark/poetry\.lock$
types: [file]
language: system
pass_filenames: false
- repo: local
# For proper type checking, Prisma client must be up-to-date.
hooks:
- id: prisma-generate
name: Prisma Generate - AutoGPT Platform - Backend
alias: prisma-generate-platform-backend
entry: bash -c 'cd autogpt_platform/backend && poetry run prisma generate'
# include everything that triggers poetry install + the prisma schema
files: ^autogpt_platform/((backend|autogpt_libs)/poetry\.lock|backend/schema.prisma)$
types: [file]
language: system
pass_filenames: false
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.2
hooks:
- id: ruff
name: Lint (Ruff) - AutoGPT Platform - Backend
alias: ruff-lint-platform-backend
files: ^autogpt_platform/backend/
args: [--fix]
- id: ruff
name: Lint (Ruff) - AutoGPT Platform - Libs
alias: ruff-lint-platform-libs
files: ^autogpt_platform/autogpt_libs/
args: [--fix]
- repo: local
# isort needs the context of which packages are installed to function, so we
# can't use a vendored isort pre-commit hook (which runs in its own isolated venv).
hooks:
- id: isort-autogpt
name: Lint (isort) - AutoGPT
- id: isort
name: Lint (isort) - AutoGPT Platform - Backend
alias: isort-platform-backend
entry: poetry -C autogpt_platform/backend run isort
files: ^autogpt_platform/backend/
types: [file, python]
language: system
- id: isort
name: Lint (isort) - Classic - AutoGPT
alias: isort-classic-autogpt
entry: poetry -C classic/original_autogpt run isort
files: ^classic/original_autogpt/
types: [file, python]
language: system
- id: isort-forge
name: Lint (isort) - Forge
- id: isort
name: Lint (isort) - Classic - Forge
alias: isort-classic-forge
entry: poetry -C classic/forge run isort
files: ^classic/forge/
types: [file, python]
language: system
- id: isort-benchmark
name: Lint (isort) - Benchmark
- id: isort
name: Lint (isort) - Classic - Benchmark
alias: isort-classic-benchmark
entry: poetry -C classic/benchmark run isort
files: ^classic/benchmark/
types: [file, python]
@@ -51,7 +141,6 @@ repos:
hooks:
- id: black
name: Lint (Black)
language_version: python3.12
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
@@ -59,20 +148,20 @@ repos:
# them separately.
hooks:
- id: flake8
name: Lint (Flake8) - AutoGPT
alias: flake8-autogpt
name: Lint (Flake8) - Classic - AutoGPT
alias: flake8-classic-autogpt
files: ^classic/original_autogpt/(autogpt|scripts|tests)/
args: [--config=classic/original_autogpt/.flake8]
- id: flake8
name: Lint (Flake8) - Forge
alias: flake8-forge
name: Lint (Flake8) - Classic - Forge
alias: flake8-classic-forge
files: ^classic/forge/(forge|tests)/
args: [--config=classic/forge/.flake8]
- id: flake8
name: Lint (Flake8) - Benchmark
alias: flake8-benchmark
name: Lint (Flake8) - Classic - Benchmark
alias: flake8-classic-benchmark
files: ^classic/benchmark/(agbenchmark|tests)/((?!reports).)*[/.]
args: [--config=classic/benchmark/.flake8]
@@ -81,31 +170,52 @@ repos:
# project. To trigger on poetry.lock we also reset the file `types` filter.
hooks:
- id: pyright
name: Typecheck - AutoGPT
alias: pyright-autogpt
entry: poetry -C classic/original_autogpt run pyright
args: [-p, autogpt, autogpt]
name: Typecheck - AutoGPT Platform - Backend
alias: pyright-platform-backend
entry: poetry -C autogpt_platform/backend run pyright
args: [-p, autogpt_platform/backend, autogpt_platform/backend]
# include forge source (since it's a path dependency) but exclude *_test.py files:
files: ^(classic/original_autogpt/((autogpt|scripts|tests)/|poetry\.lock$)|classic/forge/(classic/forge/.*(?<!_test)\.py|poetry\.lock)$)
files: ^autogpt_platform/(backend/((backend|test)/|(\w+\.py|poetry\.lock)$)|autogpt_libs/(autogpt_libs/.*(?<!_test)\.py|poetry\.lock)$)
types: [file]
language: system
pass_filenames: false
- id: pyright
name: Typecheck - Forge
alias: pyright-forge
name: Typecheck - AutoGPT Platform - Libs
alias: pyright-platform-libs
entry: poetry -C autogpt_platform/autogpt_libs run pyright
args: [-p, autogpt_platform/autogpt_libs, autogpt_platform/autogpt_libs]
files: ^autogpt_platform/autogpt_libs/(autogpt_libs/|poetry\.lock$)
types: [file]
language: system
pass_filenames: false
- id: pyright
name: Typecheck - Classic - AutoGPT
alias: pyright-classic-autogpt
entry: poetry -C classic/original_autogpt run pyright
args: [-p, classic/original_autogpt, classic/original_autogpt]
# include forge source (since it's a path dependency) but exclude *_test.py files:
files: ^(classic/original_autogpt/((autogpt|scripts|tests)/|poetry\.lock$)|classic/forge/(forge/.*(?<!_test)\.py|poetry\.lock)$)
types: [file]
language: system
pass_filenames: false
- id: pyright
name: Typecheck - Classic - Forge
alias: pyright-classic-forge
entry: poetry -C classic/forge run pyright
args: [-p, forge, forge]
files: ^classic/forge/(classic/forge/|poetry\.lock$)
args: [-p, classic/forge, classic/forge]
files: ^classic/forge/(forge/|poetry\.lock$)
types: [file]
language: system
pass_filenames: false
- id: pyright
name: Typecheck - Benchmark
alias: pyright-benchmark
name: Typecheck - Classic - Benchmark
alias: pyright-classic-benchmark
entry: poetry -C classic/benchmark run pyright
args: [-p, benchmark, benchmark]
args: [-p, classic/benchmark, classic/benchmark]
files: ^classic/benchmark/(agbenchmark/|tests/|poetry\.lock$)
types: [file]
language: system
@@ -113,23 +223,35 @@ repos:
- repo: local
hooks:
- id: pytest-autogpt
name: Run tests - AutoGPT (excl. slow tests)
- id: pytest
name: Run tests - AutoGPT Platform - Backend
alias: pytest-platform-backend
entry: bash -c 'cd autogpt_platform/backend && poetry run pytest'
# include autogpt_libs source (since it's a path dependency) but exclude *_test.py files:
files: ^autogpt_platform/(backend/((backend|test)/|poetry\.lock$)|autogpt_libs/(autogpt_libs/.*(?<!_test)\.py|poetry\.lock)$)
language: system
pass_filenames: false
- id: pytest
name: Run tests - Classic - AutoGPT (excl. slow tests)
alias: pytest-classic-autogpt
entry: bash -c 'cd classic/original_autogpt && poetry run pytest --cov=autogpt -m "not slow" tests/unit tests/integration'
# include forge source (since it's a path dependency) but exclude *_test.py files:
files: ^(classic/original_autogpt/((autogpt|tests)/|poetry\.lock$)|classic/forge/(classic/forge/.*(?<!_test)\.py|poetry\.lock)$)
files: ^(classic/original_autogpt/((autogpt|tests)/|poetry\.lock$)|classic/forge/(forge/.*(?<!_test)\.py|poetry\.lock)$)
language: system
pass_filenames: false
- id: pytest-forge
name: Run tests - Forge (excl. slow tests)
- id: pytest
name: Run tests - Classic - Forge (excl. slow tests)
alias: pytest-classic-forge
entry: bash -c 'cd classic/forge && poetry run pytest --cov=forge -m "not slow"'
files: ^classic/forge/(classic/forge/|tests/|poetry\.lock$)
files: ^classic/forge/(forge/|tests/|poetry\.lock$)
language: system
pass_filenames: false
- id: pytest-benchmark
name: Run tests - Benchmark
- id: pytest
name: Run tests - Classic - Benchmark
alias: pytest-classic-benchmark
entry: bash -c 'cd classic/benchmark && poetry run pytest --cov=benchmark'
files: ^classic/benchmark/(agbenchmark/|tests/|poetry\.lock$)
language: system

67
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,67 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Frontend: Server Side",
"type": "node-terminal",
"request": "launch",
"cwd": "${workspaceFolder}/autogpt_platform/frontend",
"command": "yarn dev"
},
{
"name": "Frontend: Client Side",
"type": "msedge",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Frontend: Full Stack",
"type": "node-terminal",
"request": "launch",
"command": "yarn dev",
"cwd": "${workspaceFolder}/autogpt_platform/frontend",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithEdge"
}
},
{
"name": "Backend",
"type": "debugpy",
"request": "launch",
"module": "backend.app",
// "env": {
// "ENV": "dev"
// },
"envFile": "${workspaceFolder}/backend/.env",
"justMyCode": false,
"cwd": "${workspaceFolder}/autogpt_platform/backend"
},
{
"name": "Marketplace",
"type": "debugpy",
"request": "launch",
"module": "autogpt_platform.market.main",
"env": {
"ENV": "dev"
},
"envFile": "${workspaceFolder}/market/.env",
"justMyCode": false,
"cwd": "${workspaceFolder}/market"
}
],
"compounds": [
{
"name": "Everything",
"configurations": ["Backend", "Frontend: Full Stack"],
// "preLaunchTask": "${defaultBuildTask}",
"stopAll": true,
"presentation": {
"hidden": false,
"order": 0
}
}
]
}

View File

@@ -72,6 +72,14 @@ did_credentials = APIKeyCredentials(
title="Use Credits for D-ID",
expires_at=None,
)
jina_credentials = APIKeyCredentials(
id="7f26de70-ba0d-494e-ba76-238e65e7b45f",
provider="jina",
api_key=SecretStr(settings.secrets.jina_api_key),
title="Use Credits for Jina",
expires_at=None,
)
DEFAULT_CREDENTIALS = [
revid_credentials,
@@ -81,6 +89,7 @@ DEFAULT_CREDENTIALS = [
anthropic_credentials,
groq_credentials,
did_credentials,
jina_credentials,
]
@@ -124,6 +133,8 @@ class SupabaseIntegrationCredentialsStore:
all_credentials.append(anthropic_credentials)
if settings.secrets.did_api_key:
all_credentials.append(did_credentials)
if settings.secrets.jina_api_key:
all_credentials.append(jina_credentials)
return all_credentials
def get_creds_by_id(self, user_id: str, credentials_id: str) -> Credentials | None:

View File

@@ -626,13 +626,13 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"]
[[package]]
name = "gotrue"
version = "2.9.3"
version = "2.10.0"
description = "Python Client Library for Supabase Auth"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "gotrue-2.9.3-py3-none-any.whl", hash = "sha256:9d2e9c74405d879f4828e0a7b94daf167a6e109c10ae6e5c59a0e21446f6e423"},
{file = "gotrue-2.9.3.tar.gz", hash = "sha256:051551d80e642bdd2ab42cac78207745d89a2a08f429a1512d82624e675d8255"},
{file = "gotrue-2.10.0-py3-none-any.whl", hash = "sha256:768e58207488e5184ffbdc4351b7280d913daf97962f4e9f2cca05c80004b042"},
{file = "gotrue-2.10.0.tar.gz", hash = "sha256:4edf4c251da3535f2b044e23deba221e848ca1210c17d0c7a9b19f79a1e3f3c0"},
]
[package.dependencies]
@@ -986,13 +986,13 @@ files = [
[[package]]
name = "postgrest"
version = "0.17.2"
version = "0.18.0"
description = "PostgREST client for Python. This library provides an ORM interface to PostgREST."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "postgrest-0.17.2-py3-none-any.whl", hash = "sha256:f7c4f448e5a5e2d4c1dcf192edae9d1007c4261e9a6fb5116783a0046846ece2"},
{file = "postgrest-0.17.2.tar.gz", hash = "sha256:445cd4e4a191e279492549df0c4e827d32f9d01d0852599bb8a6efb0f07fcf78"},
{file = "postgrest-0.18.0-py3-none-any.whl", hash = "sha256:200baad0d23fee986b3a0ffd3e07bfe0cdd40e09760f11e8e13a6c0c2376d5fa"},
{file = "postgrest-0.18.0.tar.gz", hash = "sha256:29c1a94801a17eb9ad590189993fe5a7a6d8c1bfc11a3c9d0ce7ba146454ebb3"},
]
[package.dependencies]
@@ -1322,6 +1322,33 @@ files = [
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.7.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
{file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
{file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
{file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
{file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
{file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
{file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
]
[[package]]
name = "six"
version = "1.16.0"
@@ -1346,19 +1373,18 @@ files = [
[[package]]
name = "storage3"
version = "0.8.2"
version = "0.9.0"
description = "Supabase Storage client for Python."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "storage3-0.8.2-py3-none-any.whl", hash = "sha256:f2e995b18c77a2a9265d1a33047d43e4d6abb11eb3ca5067959f68281c305de3"},
{file = "storage3-0.8.2.tar.gz", hash = "sha256:db05d3fe8fb73bd30c814c4c4749664f37a5dfc78b629e8c058ef558c2b89f5a"},
{file = "storage3-0.9.0-py3-none-any.whl", hash = "sha256:8b2fb91f0c61583a2f4eac74a8bae67e00d41ff38095c8a6cd3f2ce5e0ab76e7"},
{file = "storage3-0.9.0.tar.gz", hash = "sha256:e16697f60894c94e1d9df0d2e4af783c1b3f7dd08c9013d61978825c624188c4"},
]
[package.dependencies]
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
python-dateutil = ">=2.8.2,<3.0.0"
typing-extensions = ">=4.2.0,<5.0.0"
[[package]]
name = "strenum"
@@ -1378,32 +1404,32 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
[[package]]
name = "supabase"
version = "2.9.1"
version = "2.10.0"
description = "Supabase client for Python."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "supabase-2.9.1-py3-none-any.whl", hash = "sha256:a96f857a465712cb551679c1df66ba772c834f861756ce4aa2aa4cb703f6aeb7"},
{file = "supabase-2.9.1.tar.gz", hash = "sha256:51fce39c9eb50573126dabb342541ec5e1f13e7476938768f4b0ccfdb8c522cd"},
{file = "supabase-2.10.0-py3-none-any.whl", hash = "sha256:183fb23c04528593f8f81c24ceb8178f3a56bff40fec7ed873b6c55ebc2e420a"},
{file = "supabase-2.10.0.tar.gz", hash = "sha256:9ac095f8947bf60780e67c0edcbab53e2db3f6f3f022329397b093500bf2607c"},
]
[package.dependencies]
gotrue = ">=2.9.0,<3.0.0"
gotrue = ">=2.10.0,<3.0.0"
httpx = ">=0.26,<0.28"
postgrest = ">=0.17.0,<0.18.0"
postgrest = ">=0.18,<0.19"
realtime = ">=2.0.0,<3.0.0"
storage3 = ">=0.8.0,<0.9.0"
supafunc = ">=0.6.0,<0.7.0"
storage3 = ">=0.9.0,<0.10.0"
supafunc = ">=0.7.0,<0.8.0"
[[package]]
name = "supafunc"
version = "0.6.2"
version = "0.7.0"
description = "Library for Supabase Functions"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "supafunc-0.6.2-py3-none-any.whl", hash = "sha256:101b30616b0a1ce8cf938eca1df362fa4cf1deacb0271f53ebbd674190fb0da5"},
{file = "supafunc-0.6.2.tar.gz", hash = "sha256:c7dfa20db7182f7fe4ae436e94e05c06cd7ed98d697fed75d68c7b9792822adc"},
{file = "supafunc-0.7.0-py3-none-any.whl", hash = "sha256:4160260dc02bdd906be1e2ffd7cb3ae8b74ae437c892bb475352b6a99d9ff8eb"},
{file = "supafunc-0.7.0.tar.gz", hash = "sha256:5b1c415fba1395740b2b4eedd1d786384bd58b98f6333a11ba7889820a48b6a7"},
]
[package.dependencies]
@@ -1724,4 +1750,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<4.0"
content-hash = "f80654aae542b1f2f3a44a01f197f87ffbaea52f474dd2cc2b72b8d56b155563"
content-hash = "406a67ad0e7a03e7fa8bc6fb59a071c3a4b2a1ac9eb54a8b8d2eff09b26fe527"

View File

@@ -15,11 +15,18 @@ pydantic-settings = "^2.6.1"
pyjwt = "^2.8.0"
python = ">=3.10,<4.0"
python-dotenv = "^1.0.1"
supabase = "^2.9.1"
supabase = "^2.10.0"
[tool.poetry.group.dev.dependencies]
redis = "^5.2.0"
ruff = "^0.7.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
line-length = 88
[tool.ruff.lint]
extend-select = ["I"] # sort dependencies

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-buster AS builder
FROM python:3.11.10-slim-bookworm AS builder
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
@@ -35,7 +35,7 @@ COPY autogpt_platform/backend/schema.prisma ./
RUN poetry config virtualenvs.create false \
&& poetry run prisma generate
FROM python:3.11-slim-buster AS server_dependencies
FROM python:3.11.10-slim-bookworm AS server_dependencies
WORKDIR /app

View File

@@ -0,0 +1,100 @@
import logging
from autogpt_libs.utils.cache import thread_cached
from backend.data.block import (
Block,
BlockCategory,
BlockInput,
BlockOutput,
BlockSchema,
BlockType,
get_block,
)
from backend.data.execution import ExecutionStatus
from backend.data.model import SchemaField
logger = logging.getLogger(__name__)
@thread_cached
def get_executor_manager_client():
from backend.executor import ExecutionManager
from backend.util.service import get_service_client
return get_service_client(ExecutionManager)
@thread_cached
def get_event_bus():
from backend.data.queue import RedisExecutionEventBus
return RedisExecutionEventBus()
class AgentExecutorBlock(Block):
class Input(BlockSchema):
user_id: str = SchemaField(description="User ID")
graph_id: str = SchemaField(description="Graph ID")
graph_version: int = SchemaField(description="Graph Version")
data: BlockInput = SchemaField(description="Input data for the graph")
input_schema: dict = SchemaField(description="Input schema for the graph")
output_schema: dict = SchemaField(description="Output schema for the graph")
class Output(BlockSchema):
pass
def __init__(self):
super().__init__(
id="e189baac-8c20-45a1-94a7-55177ea42565",
description="Executes an existing agent inside your agent",
input_schema=AgentExecutorBlock.Input,
output_schema=AgentExecutorBlock.Output,
block_type=BlockType.AGENT,
categories={BlockCategory.AGENT},
)
def run(self, input_data: Input, **kwargs) -> BlockOutput:
executor_manager = get_executor_manager_client()
event_bus = get_event_bus()
graph_exec = executor_manager.add_execution(
graph_id=input_data.graph_id,
graph_version=input_data.graph_version,
user_id=input_data.user_id,
data=input_data.data,
)
log_id = f"Graph #{input_data.graph_id}-V{input_data.graph_version}, exec-id: {graph_exec.graph_exec_id}"
logger.info(f"Starting execution of {log_id}")
for event in event_bus.listen(
graph_id=graph_exec.graph_id, graph_exec_id=graph_exec.graph_exec_id
):
logger.info(
f"Execution {log_id} produced input {event.input_data} output {event.output_data}"
)
if not event.node_id:
if event.status in [ExecutionStatus.COMPLETED, ExecutionStatus.FAILED]:
logger.info(f"Execution {log_id} ended with status {event.status}")
break
else:
continue
if not event.block_id:
logger.warning(f"{log_id} received event without block_id {event}")
continue
block = get_block(event.block_id)
if not block or block.block_type != BlockType.OUTPUT:
continue
output_name = event.input_data.get("name")
if not output_name:
logger.warning(f"{log_id} produced an output with no name {event}")
continue
for output_data in event.output_data.get("output", []):
logger.info(f"Execution {log_id} produced {output_name}: {output_data}")
yield output_name, output_data

View File

@@ -0,0 +1,224 @@
import logging
import time
from enum import Enum
from typing import Literal
import replicate
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
logger = logging.getLogger(__name__)
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="replicate",
api_key=SecretStr("mock-replicate-api-key"),
title="Mock Replicate API key",
expires_at=None,
)
TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.type,
}
# Model version enum
class MusicGenModelVersion(str, Enum):
STEREO_LARGE = "stereo-large"
MELODY_LARGE = "melody-large"
LARGE = "large"
# Audio format enum
class AudioFormat(str, Enum):
WAV = "wav"
MP3 = "mp3"
# Normalization strategy enum
class NormalizationStrategy(str, Enum):
LOUDNESS = "loudness"
CLIP = "clip"
PEAK = "peak"
RMS = "rms"
class AIMusicGeneratorBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput[Literal["replicate"], Literal["api_key"]] = (
CredentialsField(
provider="replicate",
supported_credential_types={"api_key"},
description="The Replicate integration can be used with "
"any API key with sufficient permissions for the blocks it is used on.",
)
)
prompt: str = SchemaField(
description="A description of the music you want to generate",
placeholder="e.g., 'An upbeat electronic dance track with heavy bass'",
title="Prompt",
)
model_version: MusicGenModelVersion = SchemaField(
description="Model to use for generation",
default=MusicGenModelVersion.STEREO_LARGE,
title="Model Version",
)
duration: int = SchemaField(
description="Duration of the generated audio in seconds",
default=8,
title="Duration",
)
temperature: float = SchemaField(
description="Controls the 'conservativeness' of the sampling process. Higher temperature means more diversity",
default=1.0,
title="Temperature",
)
top_k: int = SchemaField(
description="Reduces sampling to the k most likely tokens",
default=250,
title="Top K",
)
top_p: float = SchemaField(
description="Reduces sampling to tokens with cumulative probability of p. When set to 0 (default), top_k sampling is used",
default=0.0,
title="Top P",
)
classifier_free_guidance: int = SchemaField(
description="Increases the influence of inputs on the output. Higher values produce lower-variance outputs that adhere more closely to inputs",
default=3,
title="Classifier Free Guidance",
)
output_format: AudioFormat = SchemaField(
description="Output format for generated audio",
default=AudioFormat.WAV,
title="Output Format",
)
normalization_strategy: NormalizationStrategy = SchemaField(
description="Strategy for normalizing audio",
default=NormalizationStrategy.LOUDNESS,
title="Normalization Strategy",
)
class Output(BlockSchema):
result: str = SchemaField(description="URL of the generated audio file")
error: str = SchemaField(description="Error message if the model run failed")
def __init__(self):
super().__init__(
id="44f6c8ad-d75c-4ae1-8209-aad1c0326928",
description="This block generates music using Meta's MusicGen model on Replicate.",
categories={BlockCategory.AI},
input_schema=AIMusicGeneratorBlock.Input,
output_schema=AIMusicGeneratorBlock.Output,
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"prompt": "An upbeat electronic dance track with heavy bass",
"model_version": MusicGenModelVersion.STEREO_LARGE,
"duration": 8,
"temperature": 1.0,
"top_k": 250,
"top_p": 0.0,
"classifier_free_guidance": 3,
"output_format": AudioFormat.WAV,
"normalization_strategy": NormalizationStrategy.LOUDNESS,
},
test_output=[
(
"result",
"https://replicate.com/output/generated-audio-url.wav",
),
],
test_mock={
"run_model": lambda api_key, model_version, prompt, duration, temperature, top_k, top_p, classifier_free_guidance, output_format, normalization_strategy: "https://replicate.com/output/generated-audio-url.wav",
},
test_credentials=TEST_CREDENTIALS,
)
def run(
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
) -> BlockOutput:
max_retries = 3
retry_delay = 5 # seconds
last_error = None
for attempt in range(max_retries):
try:
logger.debug(
f"[AIMusicGeneratorBlock] - Running model (attempt {attempt + 1})"
)
result = self.run_model(
api_key=credentials.api_key,
model_version=input_data.model_version,
prompt=input_data.prompt,
duration=input_data.duration,
temperature=input_data.temperature,
top_k=input_data.top_k,
top_p=input_data.top_p,
classifier_free_guidance=input_data.classifier_free_guidance,
output_format=input_data.output_format,
normalization_strategy=input_data.normalization_strategy,
)
if result and result != "No output received":
yield "result", result
return
else:
last_error = "Model returned empty or invalid response"
raise ValueError(last_error)
except Exception as e:
last_error = f"Unexpected error: {str(e)}"
logger.error(f"[AIMusicGeneratorBlock] - Error: {last_error}")
if attempt < max_retries - 1:
time.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(
self,
api_key: SecretStr,
model_version: MusicGenModelVersion,
prompt: str,
duration: int,
temperature: float,
top_k: int,
top_p: float,
classifier_free_guidance: int,
output_format: AudioFormat,
normalization_strategy: NormalizationStrategy,
):
# Initialize Replicate client with the API key
client = replicate.Client(api_token=api_key.get_secret_value())
# Run the model with parameters
output = client.run(
"meta/musicgen:671ac645ce5e552cc63a54a2bbff63fcf798043055d2dac5fc9e36a837eedcfb",
input={
"prompt": prompt,
"model_version": model_version,
"duration": duration,
"temperature": temperature,
"top_k": top_k,
"top_p": top_p,
"classifier_free_guidance": classifier_free_guidance,
"output_format": output_format,
"normalization_strategy": normalization_strategy,
},
)
# Handle the output
if isinstance(output, list) and len(output) > 0:
result_url = output[0] # If output is a list, get the first element
elif isinstance(output, str):
result_url = output # If output is a string, use it directly
else:
result_url = (
"No output received" # Fallback message if output is not as expected
)
return result_url

View File

@@ -3,12 +3,12 @@ import time
from enum import Enum
from typing import Literal
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
from backend.util.request import requests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
@@ -217,7 +217,6 @@ class AIShortformVideoCreatorBlock(Block):
url = "https://webhook.site/token"
headers = {"Accept": "application/json", "Content-Type": "application/json"}
response = requests.post(url, headers=headers)
response.raise_for_status()
webhook_data = response.json()
return webhook_data["uuid"], f"https://webhook.site/{webhook_data['uuid']}"
@@ -228,14 +227,12 @@ class AIShortformVideoCreatorBlock(Block):
logger.debug(
f"API Response Status Code: {response.status_code}, Content: {response.text}"
)
response.raise_for_status()
return response.json()
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.raise_for_status()
return response.json()
def wait_for_video(

View File

@@ -233,7 +233,9 @@ class AgentOutputBlock(Block):
)
name: str = SchemaField(description="The name of the output.")
title: str | None = SchemaField(
description="The title of the input.", default=None, advanced=True
description="The title of the output.",
default=None,
advanced=True,
)
description: str | None = SchemaField(
description="The description of the output.",
@@ -262,7 +264,7 @@ class AgentOutputBlock(Block):
def __init__(self):
super().__init__(
id="363ae599-353e-4804-937e-b2ee3cef3da4",
description=("Stores the output of the graph for users to see."),
description="Stores the output of the graph for users to see.",
input_schema=AgentOutputBlock.Input,
output_schema=AgentOutputBlock.Output,
test_input=[

View File

@@ -0,0 +1,43 @@
from urllib.parse import urlparse
from backend.blocks.github._auth import GithubCredentials
from backend.util.request import Requests
def _convert_to_api_url(url: str) -> str:
"""
Converts a standard GitHub URL to the corresponding GitHub API URL.
Handles repository URLs, issue URLs, pull request URLs, and more.
"""
parsed_url = urlparse(url)
path_parts = parsed_url.path.strip("/").split("/")
if len(path_parts) >= 2:
owner, repo = path_parts[0], path_parts[1]
api_base = f"https://api.github.com/repos/{owner}/{repo}"
if len(path_parts) > 2:
additional_path = "/".join(path_parts[2:])
api_url = f"{api_base}/{additional_path}"
else:
# Repository base URL
api_url = api_base
else:
raise ValueError("Invalid GitHub URL format.")
return api_url
def _get_headers(credentials: GithubCredentials) -> dict[str, str]:
return {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
def get_api(credentials: GithubCredentials) -> Requests:
return Requests(
trusted_origins=["https://api.github.com", "https://github.com"],
extra_url_validator=_convert_to_api_url,
extra_headers=_get_headers(credentials),
)

View File

@@ -1,9 +1,11 @@
import requests
from urllib.parse import urlparse
from typing_extensions import TypedDict
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
from ._api import get_api
from ._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
@@ -13,6 +15,10 @@ from ._auth import (
)
def is_github_url(url: str) -> bool:
return urlparse(url).netloc == "github.com"
# --8<-- [start:GithubCommentBlockExample]
class GithubCommentBlock(Block):
class Input(BlockSchema):
@@ -62,27 +68,10 @@ class GithubCommentBlock(Block):
def post_comment(
credentials: GithubCredentials, issue_url: str, body_text: str
) -> tuple[int, str]:
if "/pull/" in issue_url:
api_url = (
issue_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/issues/"
)
+ "/comments"
)
else:
api_url = (
issue_url.replace("github.com", "api.github.com/repos") + "/comments"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
data = {"body": body_text}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
comments_url = issue_url + "/comments"
response = api.post(comments_url, json=data)
comment = response.json()
return comment["id"], comment["html_url"]
@@ -156,16 +145,10 @@ class GithubMakeIssueBlock(Block):
def create_issue(
credentials: GithubCredentials, repo_url: str, title: str, body: str
) -> tuple[int, str]:
api_url = repo_url.replace("github.com", "api.github.com/repos") + "/issues"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
data = {"title": title, "body": body}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
issues_url = repo_url + "/issues"
response = api.post(issues_url, json=data)
issue = response.json()
return issue["number"], issue["html_url"]
@@ -232,21 +215,12 @@ class GithubReadIssueBlock(Block):
def read_issue(
credentials: GithubCredentials, issue_url: str
) -> tuple[str, str, str]:
api_url = issue_url.replace("github.com", "api.github.com/repos")
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
response = 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(
@@ -318,20 +292,13 @@ class GithubListIssuesBlock(Block):
def list_issues(
credentials: GithubCredentials, repo_url: str
) -> list[Output.IssueItem]:
api_url = repo_url.replace("github.com", "api.github.com/repos") + "/issues"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
issues_url = repo_url + "/issues"
response = 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(
@@ -385,28 +352,10 @@ class GithubAddLabelBlock(Block):
@staticmethod
def add_label(credentials: GithubCredentials, issue_url: str, label: str) -> str:
# Convert the provided GitHub URL to the API URL
if "/pull/" in issue_url:
api_url = (
issue_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/issues/"
)
+ "/labels"
)
else:
api_url = (
issue_url.replace("github.com", "api.github.com/repos") + "/labels"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
data = {"labels": [label]}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
labels_url = issue_url + "/labels"
api.post(labels_url, json=data)
return "Label added successfully"
def run(
@@ -463,31 +412,9 @@ class GithubRemoveLabelBlock(Block):
@staticmethod
def remove_label(credentials: GithubCredentials, issue_url: str, label: str) -> str:
# Convert the provided GitHub URL to the API URL
if "/pull/" in issue_url:
api_url = (
issue_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/issues/"
)
+ f"/labels/{label}"
)
else:
api_url = (
issue_url.replace("github.com", "api.github.com/repos")
+ f"/labels/{label}"
)
# Log the constructed API URL for debugging
print(f"Constructed API URL: {api_url}")
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.delete(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
label_url = issue_url + f"/labels/{label}"
api.delete(label_url)
return "Label removed successfully"
def run(
@@ -550,23 +477,10 @@ class GithubAssignIssueBlock(Block):
issue_url: str,
assignee: str,
) -> str:
# Extracting repo path and issue number from the issue URL
repo_path, issue_number = issue_url.replace("https://github.com/", "").split(
"/issues/"
)
api_url = (
f"https://api.github.com/repos/{repo_path}/issues/{issue_number}/assignees"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
assignees_url = issue_url + "/assignees"
data = {"assignees": [assignee]}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
api.post(assignees_url, json=data)
return "Issue assigned successfully"
def run(
@@ -629,23 +543,10 @@ class GithubUnassignIssueBlock(Block):
issue_url: str,
assignee: str,
) -> str:
# Extracting repo path and issue number from the issue URL
repo_path, issue_number = issue_url.replace("https://github.com/", "").split(
"/issues/"
)
api_url = (
f"https://api.github.com/repos/{repo_path}/issues/{issue_number}/assignees"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
assignees_url = issue_url + "/assignees"
data = {"assignees": [assignee]}
response = requests.delete(api_url, headers=headers, json=data)
response.raise_for_status()
api.delete(assignees_url, json=data)
return "Issue unassigned successfully"
def run(

View File

@@ -1,9 +1,9 @@
import requests
from typing_extensions import TypedDict
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
from ._api import get_api
from ._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
@@ -64,20 +64,13 @@ class GithubListPullRequestsBlock(Block):
@staticmethod
def list_prs(credentials: GithubCredentials, repo_url: str) -> list[Output.PRItem]:
api_url = repo_url.replace("github.com", "api.github.com/repos") + "/pulls"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
pulls_url = repo_url + "/pulls"
response = 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(
@@ -110,7 +103,11 @@ class GithubMakePullRequestBlock(Block):
placeholder="Enter the pull request body",
)
head: str = SchemaField(
description="The name of the branch where your changes are implemented. For cross-repository pull requests in the same network, namespace head with a user like this: username:branch.",
description=(
"The name of the branch where your changes are implemented. "
"For cross-repository pull requests in the same network, "
"namespace head with a user like this: username:branch."
),
placeholder="Enter the head branch",
)
base: str = SchemaField(
@@ -162,17 +159,10 @@ class GithubMakePullRequestBlock(Block):
head: str,
base: str,
) -> tuple[int, str]:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/pulls"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
pulls_url = repo_url + "/pulls"
data = {"title": title, "body": body, "head": head, "base": base}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
response = api.post(pulls_url, json=data)
pr_data = response.json()
return pr_data["number"], pr_data["html_url"]
@@ -194,13 +184,8 @@ class GithubMakePullRequestBlock(Block):
)
yield "number", number
yield "url", url
except requests.exceptions.HTTPError as http_err:
if http_err.response.status_code == 422:
error_details = http_err.response.json()
error_message = error_details.get("message", "Unknown error")
else:
error_message = str(http_err)
raise RuntimeError(f"Failed to create pull request: {error_message}")
except Exception as e:
yield "error", str(e)
class GithubReadPullRequestBlock(Block):
@@ -255,42 +240,21 @@ class GithubReadPullRequestBlock(Block):
@staticmethod
def read_pr(credentials: GithubCredentials, pr_url: str) -> tuple[str, str, str]:
api_url = pr_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/issues/"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
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)
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")
return title, body, author
@staticmethod
def read_pr_changes(credentials: GithubCredentials, pr_url: str) -> str:
api_url = (
pr_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/pulls/"
)
+ "/files"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
files_url = pr_url + "/files"
response = api.get(files_url)
files = response.json()
changes = []
for file in files:
@@ -298,7 +262,6 @@ class GithubReadPullRequestBlock(Block):
patch = file.get("patch")
if filename and patch:
changes.append(f"File: {filename}\n{patch}")
return "\n\n".join(changes)
def run(
@@ -367,23 +330,10 @@ class GithubAssignPRReviewerBlock(Block):
def assign_reviewer(
credentials: GithubCredentials, pr_url: str, reviewer: str
) -> str:
# Convert the PR URL to the appropriate API endpoint
api_url = (
pr_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/pulls/"
)
+ "/requested_reviewers"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
reviewers_url = pr_url + "/requested_reviewers"
data = {"reviewers": [reviewer]}
response = requests.post(api_url, headers=headers, json=data)
response.raise_for_status()
api.post(reviewers_url, json=data)
return "Reviewer assigned successfully"
def run(
@@ -400,17 +350,8 @@ class GithubAssignPRReviewerBlock(Block):
input_data.reviewer,
)
yield "status", status
except requests.exceptions.HTTPError as http_err:
if http_err.response.status_code == 422:
error_msg = (
"Failed to assign reviewer: "
f"The reviewer '{input_data.reviewer}' may not have permission "
"or the pull request is not in a valid state. "
f"Detailed error: {http_err.response.text}"
)
else:
error_msg = f"HTTP error: {http_err} - {http_err.response.text}"
raise RuntimeError(error_msg)
except Exception as e:
yield "error", str(e)
class GithubUnassignPRReviewerBlock(Block):
@@ -456,21 +397,10 @@ class GithubUnassignPRReviewerBlock(Block):
def unassign_reviewer(
credentials: GithubCredentials, pr_url: str, reviewer: str
) -> str:
api_url = (
pr_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/pulls/"
)
+ "/requested_reviewers"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
api = get_api(credentials)
reviewers_url = pr_url + "/requested_reviewers"
data = {"reviewers": [reviewer]}
response = requests.delete(api_url, headers=headers, json=data)
response.raise_for_status()
api.delete(reviewers_url, json=data)
return "Reviewer unassigned successfully"
def run(
@@ -480,12 +410,15 @@ class GithubUnassignPRReviewerBlock(Block):
credentials: GithubCredentials,
**kwargs,
) -> BlockOutput:
status = self.unassign_reviewer(
credentials,
input_data.pr_url,
input_data.reviewer,
)
yield "status", status
try:
status = self.unassign_reviewer(
credentials,
input_data.pr_url,
input_data.reviewer,
)
yield "status", status
except Exception as e:
yield "error", str(e)
class GithubListPRReviewersBlock(Block):
@@ -544,26 +477,14 @@ class GithubListPRReviewersBlock(Block):
def list_reviewers(
credentials: GithubCredentials, pr_url: str
) -> list[Output.ReviewerItem]:
api_url = (
pr_url.replace("github.com", "api.github.com/repos").replace(
"/pull/", "/pulls/"
)
+ "/requested_reviewers"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
reviewers_url = pr_url + "/requested_reviewers"
response = api.get(reviewers_url)
data = response.json()
reviewers: list[GithubListPRReviewersBlock.Output.ReviewerItem] = [
{"username": reviewer["login"], "url": reviewer["html_url"]}
for reviewer in data.get("users", [])
]
return reviewers
def run(

View File

@@ -1,11 +1,11 @@
import base64
import requests
from typing_extensions import TypedDict
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
from ._api import get_api
from ._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
@@ -68,17 +68,11 @@ class GithubListTagsBlock(Block):
def list_tags(
credentials: GithubCredentials, repo_url: str
) -> list[Output.TagItem]:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/tags"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
tags_url = repo_url + "/tags"
response = api.get(tags_url)
data = response.json()
repo_path = repo_url.replace("https://github.com/", "")
tags: list[GithubListTagsBlock.Output.TagItem] = [
{
"name": tag["name"],
@@ -86,7 +80,6 @@ class GithubListTagsBlock(Block):
}
for tag in data
]
return tags
def run(
@@ -157,20 +150,18 @@ class GithubListBranchesBlock(Block):
def list_branches(
credentials: GithubCredentials, repo_url: str
) -> list[Output.BranchItem]:
api_url = repo_url.replace("github.com", "api.github.com/repos") + "/branches"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
branches_url = repo_url + "/branches"
response = api.get(branches_url)
data = response.json()
repo_path = repo_url.replace("https://github.com/", "")
branches: list[GithubListBranchesBlock.Output.BranchItem] = [
{"name": branch["name"], "url": branch["commit"]["url"]} for branch in data
{
"name": branch["name"],
"url": f"https://github.com/{repo_path}/tree/{branch['name']}",
}
for branch in data
]
return branches
def run(
@@ -246,6 +237,8 @@ class GithubListDiscussionsBlock(Block):
def list_discussions(
credentials: GithubCredentials, repo_url: str, num_discussions: int
) -> list[Output.DiscussionItem]:
api = get_api(credentials)
# GitHub GraphQL API endpoint is different; we'll use api.post with custom URL
repo_path = repo_url.replace("https://github.com/", "")
owner, repo = repo_path.split("/")
query = """
@@ -261,24 +254,15 @@ class GithubListDiscussionsBlock(Block):
}
"""
variables = {"owner": owner, "repo": repo, "num": num_discussions}
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.post(
response = api.post(
"https://api.github.com/graphql",
json={"query": query, "variables": variables},
headers=headers,
)
response.raise_for_status()
data = response.json()
discussions: list[GithubListDiscussionsBlock.Output.DiscussionItem] = [
{"title": discussion["title"], "url": discussion["url"]}
for discussion in data["data"]["repository"]["discussions"]["nodes"]
]
return discussions
def run(
@@ -348,21 +332,13 @@ class GithubListReleasesBlock(Block):
def list_releases(
credentials: GithubCredentials, repo_url: str
) -> list[Output.ReleaseItem]:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/releases"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
releases_url = repo_url + "/releases"
response = 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(
@@ -432,16 +408,9 @@ class GithubReadFileBlock(Block):
def read_file(
credentials: GithubCredentials, repo_url: str, file_path: str, branch: str
) -> tuple[str, int]:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/contents/{file_path}?ref={branch}"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
content_url = repo_url + f"/contents/{file_path}?ref={branch}"
response = api.get(content_url)
content = response.json()
if isinstance(content, list):
@@ -549,46 +518,33 @@ class GithubReadFolderBlock(Block):
def read_folder(
credentials: GithubCredentials, repo_url: str, folder_path: str, branch: str
) -> tuple[list[Output.FileEntry], list[Output.DirEntry]]:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/contents/{folder_path}?ref={branch}"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
contents_url = repo_url + f"/contents/{folder_path}?ref={branch}"
response = api.get(contents_url)
content = response.json()
if isinstance(content, list):
# Multiple entries of different types exist at this path
if not (dir := next((d for d in content if d["type"] == "dir"), None)):
raise TypeError("Not a folder")
content = dir
if content["type"] != "dir":
if not isinstance(content, list):
raise TypeError("Not a folder")
return (
[
GithubReadFolderBlock.Output.FileEntry(
name=entry["name"],
path=entry["path"],
size=entry["size"],
)
for entry in content["entries"]
if entry["type"] == "file"
],
[
GithubReadFolderBlock.Output.DirEntry(
name=entry["name"],
path=entry["path"],
)
for entry in content["entries"]
if entry["type"] == "dir"
],
)
files = [
GithubReadFolderBlock.Output.FileEntry(
name=entry["name"],
path=entry["path"],
size=entry["size"],
)
for entry in content
if entry["type"] == "file"
]
dirs = [
GithubReadFolderBlock.Output.DirEntry(
name=entry["name"],
path=entry["path"],
)
for entry in content
if entry["type"] == "dir"
]
return files, dirs
def run(
self,
@@ -656,26 +612,16 @@ class GithubMakeBranchBlock(Block):
new_branch: str,
source_branch: str,
) -> str:
repo_path = repo_url.replace("https://github.com/", "")
ref_api_url = (
f"https://api.github.com/repos/{repo_path}/git/refs/heads/{source_branch}"
)
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.get(ref_api_url, headers=headers)
response.raise_for_status()
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"]
create_branch_api_url = f"https://api.github.com/repos/{repo_path}/git/refs"
# Create the new branch
create_ref_url = repo_url + "/git/refs"
data = {"ref": f"refs/heads/{new_branch}", "sha": sha}
response = requests.post(create_branch_api_url, headers=headers, json=data)
response.raise_for_status()
response = api.post(create_ref_url, json=data)
return "Branch created successfully"
def run(
@@ -735,16 +681,9 @@ class GithubDeleteBranchBlock(Block):
def delete_branch(
credentials: GithubCredentials, repo_url: str, branch: str
) -> str:
repo_path = repo_url.replace("https://github.com/", "")
api_url = f"https://api.github.com/repos/{repo_path}/git/refs/heads/{branch}"
headers = {
"Authorization": credentials.bearer(),
"Accept": "application/vnd.github.v3+json",
}
response = requests.delete(api_url, headers=headers)
response.raise_for_status()
api = get_api(credentials)
ref_url = repo_url + f"/git/refs/heads/{branch}"
api.delete(ref_url)
return "Branch deleted successfully"
def run(

View File

@@ -0,0 +1,14 @@
from typing import Any, Optional
from backend.util.request import requests
class GetRequest:
@classmethod
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

View File

@@ -1,10 +1,10 @@
import json
from enum import Enum
import requests
from typing import Any
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
from backend.util.request import requests
class HttpMethod(Enum):
@@ -31,9 +31,14 @@ class SendWebRequestBlock(Block):
description="The headers to include in the request",
default={},
)
body: object = SchemaField(
json_format: bool = SchemaField(
title="JSON format",
description="Whether to send and receive body as JSON",
default=True,
)
body: Any = SchemaField(
description="The body of the request",
default={},
default=None,
)
class Output(BlockSchema):
@@ -58,13 +63,16 @@ class SendWebRequestBlock(Block):
input_data.method.value,
input_data.url,
headers=input_data.headers,
json=input_data.body,
json=input_data.body if input_data.json_format else None,
data=input_data.body if not input_data.json_format else None,
)
result = response.json() if input_data.json_format else response.text
if response.status_code // 100 == 2:
yield "response", response.json()
yield "response", result
elif response.status_code // 100 == 4:
yield "client_error", response.json()
yield "client_error", result
elif response.status_code // 100 == 5:
yield "server_error", response.json()
yield "server_error", result
else:
raise ValueError(f"Unexpected status code: {response.status_code}")

View File

@@ -1,12 +1,13 @@
from enum import Enum
from typing import Any, Dict, Literal, Optional
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from requests.exceptions import RequestException
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
from backend.util.request import requests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
@@ -242,9 +243,8 @@ class IdeogramModelBlock(Block):
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
return response.json()["data"][0]["url"]
except requests.exceptions.RequestException as e:
except RequestException as e:
raise Exception(f"Failed to fetch image: {str(e)}")
def upscale_image(self, api_key: SecretStr, image_url: str):
@@ -256,7 +256,6 @@ class IdeogramModelBlock(Block):
try:
# Step 1: Download the image from the provided URL
image_response = requests.get(image_url)
image_response.raise_for_status()
# Step 2: Send the downloaded image to the upscale API
files = {
@@ -272,8 +271,7 @@ class IdeogramModelBlock(Block):
files=files,
)
response.raise_for_status()
return response.json()["data"][0]["url"]
except requests.exceptions.RequestException as e:
except RequestException as e:
raise Exception(f"Failed to upscale image: {str(e)}")

View File

@@ -11,6 +11,20 @@ JinaCredentialsInput = CredentialsMetaInput[
Literal["api_key"],
]
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="jina",
api_key=SecretStr("mock-jina-api-key"),
title="Mock Jina API key",
expires_at=None,
)
TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.type,
}
def JinaCredentialsField() -> JinaCredentialsInput:
"""

View File

@@ -1,5 +1,3 @@
import requests
from backend.blocks.jina._auth import (
JinaCredentials,
JinaCredentialsField,
@@ -7,6 +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
class JinaChunkingBlock(Block):
@@ -57,7 +56,6 @@ class JinaChunkingBlock(Block):
}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
result = response.json()
all_chunks.extend(result.get("chunks", []))

View File

@@ -1,5 +1,3 @@
import requests
from backend.blocks.jina._auth import (
JinaCredentials,
JinaCredentialsField,
@@ -7,6 +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
class JinaEmbeddingBlock(Block):

View File

@@ -0,0 +1,57 @@
from groq._utils._utils import quote
from backend.blocks.jina._auth import (
TEST_CREDENTIALS,
TEST_CREDENTIALS_INPUT,
JinaCredentials,
JinaCredentialsField,
JinaCredentialsInput,
)
from backend.blocks.search import GetRequest
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
class SearchTheWebBlock(Block, GetRequest):
class Input(BlockSchema):
credentials: JinaCredentialsInput = JinaCredentialsField()
query: str = SchemaField(description="The search query to search the web for")
class Output(BlockSchema):
results: str = SchemaField(
description="The search results including content from top 5 URLs"
)
error: str = SchemaField(description="Error message if the search fails")
def __init__(self):
super().__init__(
id="87840993-2053-44b7-8da4-187ad4ee518c",
description="This block searches the internet for the given search query.",
categories={BlockCategory.SEARCH},
input_schema=SearchTheWebBlock.Input,
output_schema=SearchTheWebBlock.Output,
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"query": "Artificial Intelligence",
},
test_credentials=TEST_CREDENTIALS,
test_output=("results", "search content"),
test_mock={"get_request": lambda *args, **kwargs: "search content"},
)
def run(
self, input_data: Input, *, credentials: JinaCredentials, **kwargs
) -> BlockOutput:
# Encode the search query
encoded_query = quote(input_data.query)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {credentials.api_key.get_secret_value()}",
}
# 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)
# Output the search results
yield "results", results

View File

@@ -1,7 +1,6 @@
from enum import Enum
from typing import List, Literal
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
@@ -13,6 +12,7 @@ from backend.data.model import (
SchemaField,
SecretField,
)
from backend.util.request import requests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",

View File

@@ -1,4 +1,5 @@
from typing import Literal
import uuid
from typing import Any, Literal
from autogpt_libs.supabase_integration_credentials_store import APIKeyCredentials
from pinecone import Pinecone, ServerlessSpec
@@ -98,10 +99,14 @@ class PineconeQueryBlock(Block):
include_metadata: bool = SchemaField(
description="Whether to include metadata in the response", default=True
)
host: str = SchemaField(description="Host for pinecone")
host: str = SchemaField(description="Host for pinecone", default="")
idx_name: str = SchemaField(description="Index name for pinecone")
class Output(BlockSchema):
results: dict = SchemaField(description="Query results from Pinecone")
results: Any = SchemaField(description="Query results from Pinecone")
combined_results: Any = SchemaField(
description="Combined results from Pinecone"
)
def __init__(self):
super().__init__(
@@ -119,13 +124,105 @@ class PineconeQueryBlock(Block):
credentials: APIKeyCredentials,
**kwargs,
) -> BlockOutput:
pc = Pinecone(api_key=credentials.api_key.get_secret_value())
idx = pc.Index(host=input_data.host)
results = idx.query(
namespace=input_data.namespace,
vector=input_data.query_vector,
top_k=input_data.top_k,
include_values=input_data.include_values,
include_metadata=input_data.include_metadata,
try:
# Create a new client instance
pc = Pinecone(api_key=credentials.api_key.get_secret_value())
# Get the index
idx = pc.Index(input_data.idx_name)
# Ensure query_vector is in correct format
query_vector = input_data.query_vector
if isinstance(query_vector, list) and len(query_vector) > 0:
if isinstance(query_vector[0], list):
query_vector = query_vector[0]
results = idx.query(
namespace=input_data.namespace,
vector=query_vector,
top_k=input_data.top_k,
include_values=input_data.include_values,
include_metadata=input_data.include_metadata,
).to_dict()
combined_text = ""
if results["matches"]:
texts = [
match["metadata"]["text"]
for match in results["matches"]
if match.get("metadata", {}).get("text")
]
combined_text = "\n\n".join(texts)
# Return both the raw matches and combined text
yield "results", {
"matches": results["matches"],
"combined_text": combined_text,
}
yield "combined_results", combined_text
except Exception as e:
error_msg = f"Error querying Pinecone: {str(e)}"
raise RuntimeError(error_msg) from e
class PineconeInsertBlock(Block):
class Input(BlockSchema):
credentials: PineconeCredentialsInput = PineconeCredentialsField()
index: str = SchemaField(description="Initialized Pinecone index")
chunks: list = SchemaField(description="List of text chunks to ingest")
embeddings: list = SchemaField(
description="List of embeddings corresponding to the chunks"
)
yield "results", results
namespace: str = SchemaField(
description="Namespace to use in Pinecone", default=""
)
metadata: dict = SchemaField(
description="Additional metadata to store with each vector", default={}
)
class Output(BlockSchema):
upsert_response: str = SchemaField(
description="Response from Pinecone upsert operation"
)
def __init__(self):
super().__init__(
id="477f2168-cd91-475a-8146-9499a5982434",
description="Upload data to a Pinecone index",
categories={BlockCategory.LOGIC},
input_schema=PineconeInsertBlock.Input,
output_schema=PineconeInsertBlock.Output,
)
def run(
self,
input_data: Input,
*,
credentials: APIKeyCredentials,
**kwargs,
) -> BlockOutput:
try:
# Create a new client instance
pc = Pinecone(api_key=credentials.api_key.get_secret_value())
# Get the index
idx = pc.Index(input_data.index)
vectors = []
for chunk, embedding in zip(input_data.chunks, input_data.embeddings):
vector_metadata = input_data.metadata.copy()
vector_metadata["text"] = chunk
vectors.append(
{
"id": str(uuid.uuid4()),
"values": embedding,
"metadata": vector_metadata,
}
)
idx.upsert(vectors=vectors, namespace=input_data.namespace)
yield "upsert_response", "successfully upserted"
except Exception as e:
error_msg = f"Error uploading to Pinecone: {str(e)}"
raise RuntimeError(error_msg) from e

View File

@@ -1,22 +1,14 @@
from typing import Any, Literal
from typing import Literal
from urllib.parse import quote
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from backend.blocks.helpers.http import GetRequest
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
class GetRequest:
@classmethod
def get_request(cls, url: str, json=False) -> Any:
response = requests.get(url)
response.raise_for_status()
return response.json() if json else response.text
class GetWikipediaSummaryBlock(Block, GetRequest):
class Input(BlockSchema):
topic: str = SchemaField(description="The topic to fetch the summary for")
@@ -48,42 +40,6 @@ class GetWikipediaSummaryBlock(Block, GetRequest):
yield "summary", response["extract"]
class SearchTheWebBlock(Block, GetRequest):
class Input(BlockSchema):
query: str = SchemaField(description="The search query to search the web for")
class Output(BlockSchema):
results: str = SchemaField(
description="The search results including content from top 5 URLs"
)
error: str = SchemaField(description="Error message if the search fails")
def __init__(self):
super().__init__(
id="87840993-2053-44b7-8da4-187ad4ee518c",
description="This block searches the internet for the given search query.",
categories={BlockCategory.SEARCH},
input_schema=SearchTheWebBlock.Input,
output_schema=SearchTheWebBlock.Output,
test_input={"query": "Artificial Intelligence"},
test_output=("results", "search content"),
test_mock={"get_request": lambda url, json: "search content"},
)
def run(self, input_data: Input, **kwargs) -> BlockOutput:
# Encode the search query
encoded_query = quote(input_data.query)
# Prepend the Jina Search URL to the encoded query
jina_search_url = f"https://s.jina.ai/{encoded_query}"
# Make the request to Jina Search
response = self.get_request(jina_search_url, json=False)
# Output the search results
yield "results", response
class ExtractWebsiteContentBlock(Block, GetRequest):
class Input(BlockSchema):
url: str = SchemaField(description="The URL to scrape the content from")

View File

@@ -1,12 +1,12 @@
import time
from typing import Literal
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
from backend.util.request import requests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
@@ -118,7 +118,6 @@ class CreateTalkingAvatarVideoBlock(Block):
"authorization": f"Basic {api_key.get_secret_value()}",
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
def get_clip_status(self, api_key: SecretStr, clip_id: str) -> dict:
@@ -128,7 +127,6 @@ class CreateTalkingAvatarVideoBlock(Block):
"authorization": f"Basic {api_key.get_secret_value()}",
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
def run(

View File

@@ -1,11 +1,11 @@
from typing import Any, Literal
import requests
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
from pydantic import SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
from backend.util.request import requests
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
@@ -86,7 +86,6 @@ class UnrealTextToSpeechBlock(Block):
}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
def run(

View File

@@ -139,7 +139,7 @@ class GetCurrentDateAndTimeBlock(Block):
class CountdownTimerBlock(Block):
class Input(BlockSchema):
input_data: Any = SchemaField(
input_message: Any = SchemaField(
advanced=False,
description="Message to output after the timer finishes",
default="timer finished",
@@ -158,7 +158,9 @@ class CountdownTimerBlock(Block):
)
class Output(BlockSchema):
output_data: Any = SchemaField(description="Message after the timer finishes")
output_message: Any = SchemaField(
description="Message after the timer finishes"
)
def __init__(self):
super().__init__(
@@ -169,11 +171,11 @@ class CountdownTimerBlock(Block):
output_schema=CountdownTimerBlock.Output,
test_input=[
{"seconds": 1},
{"input_data": "Custom message"},
{"input_message": "Custom message"},
],
test_output=[
("output_data", "timer finished"),
("output_data", "Custom message"),
("output_message", "timer finished"),
("output_message", "Custom message"),
],
)
@@ -186,4 +188,4 @@ class CountdownTimerBlock(Block):
total_seconds = seconds + minutes * 60 + hours * 3600 + days * 86400
time.sleep(total_seconds)
yield "output_data", input_data.input_data
yield "output_message", input_data.input_message

View File

@@ -34,6 +34,7 @@ class BlockType(Enum):
INPUT = "Input"
OUTPUT = "Output"
NOTE = "Note"
AGENT = "Agent"
class BlockCategory(Enum):
@@ -48,6 +49,7 @@ class BlockCategory(Enum):
COMMUNICATION = "Block that interacts with communication platforms."
DEVELOPER_TOOLS = "Developer tools such as GitHub blocks."
DATA = "Block that interacts with structured data."
AGENT = "Block that interacts with other agents."
def dict(self) -> dict[str, str]:
return {"category": self.name, "description": self.value}
@@ -299,7 +301,9 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]):
):
if output_name == "error":
raise RuntimeError(output_data)
if error := self.output_schema.validate_field(output_name, output_data):
if self.block_type == BlockType.STANDARD and (
error := self.output_schema.validate_field(output_name, output_data)
):
raise ValueError(f"Block produced an invalid output data: {error}")
yield output_name, output_data

View File

@@ -9,6 +9,7 @@ from autogpt_libs.supabase_integration_credentials_store.store import (
did_credentials,
groq_credentials,
ideogram_credentials,
jina_credentials,
openai_credentials,
replicate_credentials,
revid_credentials,
@@ -20,6 +21,7 @@ from pydantic import BaseModel
from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock
from backend.blocks.ideogram import IdeogramModelBlock
from backend.blocks.jina.search import SearchTheWebBlock
from backend.blocks.llm import (
MODEL_METADATA,
AIConversationBlock,
@@ -29,7 +31,7 @@ from backend.blocks.llm import (
LlmModel,
)
from backend.blocks.replicate_flux_advanced import ReplicateFluxAdvancedModelBlock
from backend.blocks.search import ExtractWebsiteContentBlock, SearchTheWebBlock
from backend.blocks.search import ExtractWebsiteContentBlock
from backend.blocks.talking_head import CreateTalkingAvatarVideoBlock
from backend.data.block import Block, BlockInput, get_block
from backend.util.settings import Config
@@ -143,7 +145,18 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
},
)
],
SearchTheWebBlock: [BlockCost(cost_amount=1)],
SearchTheWebBlock: [
BlockCost(
cost_amount=1,
cost_filter={
"credentials": {
"id": jina_credentials.id,
"provider": jina_credentials.provider,
"type": jina_credentials.type,
}
},
)
],
ExtractWebsiteContentBlock: [
BlockCost(cost_amount=1, cost_filter={"raw_content": False})
],

View File

@@ -64,6 +64,7 @@ class ExecutionResult(BaseModel):
graph_exec_id: str
node_exec_id: str
node_id: str
block_id: str
status: ExecutionStatus
input_data: BlockInput
output_data: CompletedBlockOutput
@@ -72,6 +73,26 @@ class ExecutionResult(BaseModel):
start_time: datetime | None
end_time: datetime | None
@staticmethod
def from_graph(graph: AgentGraphExecution):
return ExecutionResult(
graph_id=graph.agentGraphId,
graph_version=graph.agentGraphVersion,
graph_exec_id=graph.id,
node_exec_id="",
node_id="",
block_id="",
status=graph.executionStatus,
# TODO: Populate input_data & output_data from AgentNodeExecutions
# Input & Output comes AgentInputBlock & AgentOutputBlock.
input_data={},
output_data={},
add_time=graph.createdAt,
queue_time=graph.createdAt,
start_time=graph.startedAt,
end_time=graph.updatedAt,
)
@staticmethod
def from_db(execution: AgentNodeExecution):
if execution.executionData:
@@ -93,9 +114,10 @@ class ExecutionResult(BaseModel):
graph_id=graph_execution.agentGraphId if graph_execution else "",
graph_version=graph_execution.agentGraphVersion if graph_execution else 0,
graph_exec_id=execution.agentGraphExecutionId,
block_id=execution.AgentNode.agentBlockId if execution.AgentNode else "",
node_exec_id=execution.id,
node_id=execution.agentNodeId,
status=ExecutionStatus(execution.executionStatus),
status=execution.executionStatus,
input_data=input_data,
output_data=output_data,
add_time=execution.addedTime,
@@ -248,15 +270,20 @@ async def update_graph_execution_start_time(graph_exec_id: str):
async def update_graph_execution_stats(
graph_exec_id: str,
stats: dict[str, Any],
):
) -> ExecutionResult:
status = ExecutionStatus.FAILED if stats.get("error") else ExecutionStatus.COMPLETED
await AgentGraphExecution.prisma().update(
res = await AgentGraphExecution.prisma().update(
where={"id": graph_exec_id},
data={
"executionStatus": status,
"stats": json.dumps(stats),
},
)
if not res:
raise ValueError(f"Execution {graph_exec_id} not found.")
return ExecutionResult.from_graph(res)
async def update_node_execution_stats(node_exec_id: str, stats: dict[str, Any]):

View File

@@ -9,6 +9,7 @@ from prisma.models import AgentGraph, AgentGraphExecution, AgentNode, AgentNodeL
from prisma.types import AgentGraphWhereInput
from pydantic.fields import computed_field
from backend.blocks.agent import AgentExecutorBlock
from backend.blocks.basic import AgentInputBlock, AgentOutputBlock
from backend.data.block import BlockInput, BlockType, get_block, get_blocks
from backend.data.db import BaseDbModel, transaction
@@ -174,24 +175,35 @@ class Graph(BaseDbModel):
if node.id not in outbound_nodes or node.id in input_nodes
]
def reassign_ids(self, reassign_graph_id: bool = False):
def reassign_ids(self, user_id: str, reassign_graph_id: bool = False):
"""
Reassigns all IDs in the graph to new UUIDs.
This method can be used before storing a new graph to the database.
"""
self.validate_graph()
# Reassign Graph ID
id_map = {node.id: str(uuid.uuid4()) for node in self.nodes}
if reassign_graph_id:
self.id = str(uuid.uuid4())
# Reassign Node IDs
for node in self.nodes:
node.id = id_map[node.id]
# Reassign Link IDs
for link in self.links:
link.source_id = id_map[link.source_id]
link.sink_id = id_map[link.sink_id]
# Reassign User IDs for agent blocks
for node in self.nodes:
if node.block_id != AgentExecutorBlock().id:
continue
node.input_default["user_id"] = user_id
node.input_default.setdefault("data", {})
self.validate_graph()
def validate_graph(self, for_run: bool = False):
def sanitize(name):
return name.split("_#_")[0].split("_@_")[0].split("_$_")[0]
@@ -215,6 +227,7 @@ class Graph(BaseDbModel):
for_run # Skip input completion validation, unless when executing.
or block.block_type == BlockType.INPUT
or block.block_type == BlockType.OUTPUT
or block.block_type == BlockType.AGENT
):
raise ValueError(
f"Node {block.name} #{node.id} required input missing: `{name}`"
@@ -248,18 +261,26 @@ class Graph(BaseDbModel):
)
sanitized_name = sanitize(name)
vals = node.input_default
if i == 0:
fields = f"Valid output fields: {block.output_schema.get_fields()}"
fields = (
block.output_schema.get_fields()
if block.block_type != BlockType.AGENT
else vals.get("output_schema", {}).get("properties", {}).keys()
)
else:
fields = f"Valid input fields: {block.input_schema.get_fields()}"
fields = (
block.input_schema.get_fields()
if block.block_type != BlockType.AGENT
else vals.get("input_schema", {}).get("properties", {}).keys()
)
if sanitized_name not in fields:
raise ValueError(f"{suffix}, `{name}` invalid, {fields}")
fields_msg = f"Allowed fields: {fields}"
raise ValueError(f"{suffix}, `{name}` invalid, {fields_msg}")
if is_static_output_block(link.source_id):
link.is_static = True # Each value block output should be static.
# TODO: Add type compatibility check here.
@staticmethod
def from_db(graph: AgentGraph, hide_credentials: bool = False):
executions = [

View File

@@ -41,8 +41,8 @@ class DatabaseManager(AppService):
return Config().database_api_port
@expose
def send_execution_update(self, execution_result_dict: dict[Any, Any]):
self.event_queue.publish(ExecutionResult(**execution_result_dict))
def send_execution_update(self, execution_result: ExecutionResult):
self.event_queue.publish(execution_result)
@staticmethod
def exposed_run_and_wait(

View File

@@ -125,7 +125,7 @@ def execute_node(
def update_execution(status: ExecutionStatus) -> ExecutionResult:
exec_update = db_client.update_execution_status(node_exec_id, status)
db_client.send_execution_update(exec_update.model_dump())
db_client.send_execution_update(exec_update)
return exec_update
node = db_client.get_node(node_id)
@@ -251,7 +251,7 @@ def _enqueue_next_nodes(
exec_update = db_client.update_execution_status(
node_exec_id, ExecutionStatus.QUEUED, data
)
db_client.send_execution_update(exec_update.model_dump())
db_client.send_execution_update(exec_update)
return NodeExecution(
user_id=user_id,
graph_exec_id=graph_exec_id,
@@ -572,10 +572,11 @@ class Executor:
exec_stats["walltime"] = timing_info.wall_time
exec_stats["cputime"] = timing_info.cpu_time
exec_stats["error"] = str(error) if error else None
cls.db_client.update_graph_execution_stats(
result = cls.db_client.update_graph_execution_stats(
graph_exec_id=graph_exec.graph_exec_id,
stats=exec_stats,
)
cls.db_client.send_execution_update(result)
@classmethod
@time_measured
@@ -729,7 +730,7 @@ class ExecutionManager(AppService):
)
self.active_graph_runs[graph_exec_id] = (future, cancel_event)
future.add_done_callback(
lambda _: self.active_graph_runs.pop(graph_exec_id)
lambda _: self.active_graph_runs.pop(graph_exec_id, None)
)
def cleanup(self):
@@ -744,11 +745,17 @@ class ExecutionManager(AppService):
@expose
def add_execution(
self, graph_id: str, data: BlockInput, user_id: str
) -> dict[str, Any]:
graph: Graph | None = self.db_client.get_graph(graph_id, user_id=user_id)
self,
graph_id: str,
data: BlockInput,
user_id: str,
graph_version: int | None = None,
) -> GraphExecution:
graph: Graph | None = self.db_client.get_graph(
graph_id=graph_id, user_id=user_id, version=graph_version
)
if not graph:
raise Exception(f"Graph #{graph_id} not found.")
raise ValueError(f"Graph #{graph_id} not found.")
graph.validate_graph(for_run=True)
self._validate_node_input_credentials(graph, user_id)
@@ -770,7 +777,7 @@ class ExecutionManager(AppService):
input_data, error = validate_exec(node, input_data)
if input_data is None:
raise Exception(error)
raise ValueError(error)
else:
nodes_input.append((node.id, input_data))
@@ -796,7 +803,7 @@ class ExecutionManager(AppService):
exec_update = self.db_client.update_execution_status(
node_exec.node_exec_id, ExecutionStatus.QUEUED, node_exec.input_data
)
self.db_client.send_execution_update(exec_update.model_dump())
self.db_client.send_execution_update(exec_update)
graph_exec = GraphExecution(
user_id=user_id,
@@ -806,7 +813,7 @@ class ExecutionManager(AppService):
)
self.queue.add(graph_exec)
return graph_exec.model_dump()
return graph_exec
@expose
def cancel_execution(self, graph_exec_id: str) -> None:
@@ -843,7 +850,7 @@ class ExecutionManager(AppService):
exec_update = self.db_client.update_execution_status(
node_exec.node_exec_id, ExecutionStatus.FAILED
)
self.db_client.send_execution_update(exec_update.model_dump())
self.db_client.send_execution_update(exec_update)
def _validate_node_input_credentials(self, graph: Graph, user_id: str):
"""Checks all credentials for all nodes of the graph"""

View File

@@ -2,9 +2,10 @@ import time
from typing import Optional
from urllib.parse import urlencode
import requests
from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials
from backend.util.request import requests
from .base import BaseOAuthHandler
@@ -56,13 +57,12 @@ class GitHubOAuthHandler(BaseOAuthHandler):
"X-GitHub-Api-Version": "2022-11-28",
}
response = requests.delete(
requests.delete(
url=self.revoke_url.format(client_id=self.client_id),
auth=(self.client_id, self.client_secret),
headers=headers,
json={"access_token": credentials.access_token.get_secret_value()},
)
response.raise_for_status()
return True
def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials:
@@ -88,7 +88,6 @@ class GitHubOAuthHandler(BaseOAuthHandler):
}
headers = {"Accept": "application/json"}
response = requests.post(self.token_url, data=request_body, headers=headers)
response.raise_for_status()
token_data: dict = response.json()
username = self._request_username(token_data["access_token"])

View File

@@ -103,12 +103,11 @@ class GoogleOAuthHandler(BaseOAuthHandler):
def revoke_tokens(self, credentials: OAuth2Credentials) -> bool:
session = AuthorizedSession(credentials)
response = session.post(
session.post(
self.revoke_uri,
params={"token": credentials.access_token.get_secret_value()},
headers={"content-type": "application/x-www-form-urlencoded"},
)
response.raise_for_status()
return True
def _request_email(

View File

@@ -1,9 +1,10 @@
from base64 import b64encode
from urllib.parse import urlencode
import requests
from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials
from backend.util.request import requests
from .base import BaseOAuthHandler
@@ -49,7 +50,6 @@ class NotionOAuthHandler(BaseOAuthHandler):
"Accept": "application/json",
}
response = requests.post(self.token_url, json=request_body, headers=headers)
response.raise_for_status()
token_data = response.json()
# Email is only available for non-bot users
email = (

View File

@@ -209,7 +209,7 @@ async def update_graph(
400, detail="Changing is_template on an existing graph is forbidden"
)
graph.is_active = not graph.is_template
graph.reassign_ids()
graph.reassign_ids(user_id=user_id)
new_graph_version = await graph_db.create_graph(graph, user_id=user_id)
@@ -265,7 +265,7 @@ async def execute_graph(
graph_exec = execution_manager_client().add_execution(
graph_id, node_input, user_id=user_id
)
return {"id": graph_exec["graph_exec_id"]}
return {"id": graph_exec.graph_exec_id}
except Exception as e:
msg = e.__str__().encode().decode("unicode_escape")
raise HTTPException(status_code=400, detail=msg)
@@ -403,7 +403,7 @@ async def do_create_graph(
graph.is_template = is_template
graph.is_active = not is_template
graph.reassign_ids(reassign_graph_id=True)
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
return await graph_db.create_graph(graph, user_id=user_id)

View File

@@ -0,0 +1,132 @@
import ipaddress
import socket
from typing import Callable
from urllib.parse import urlparse
import requests as req
from backend.util.settings import Config
# List of IP networks to block
BLOCKED_IP_NETWORKS = [
ipaddress.ip_network("0.0.0.0/8"), # "This" Network
ipaddress.ip_network("10.0.0.0/8"), # Private-Use
ipaddress.ip_network("127.0.0.0/8"), # Loopback
ipaddress.ip_network("169.254.0.0/16"), # Link Local
ipaddress.ip_network("172.16.0.0/12"), # Private-Use
ipaddress.ip_network("192.168.0.0/16"), # Private-Use
ipaddress.ip_network("224.0.0.0/4"), # Multicast
ipaddress.ip_network("240.0.0.0/4"), # Reserved for Future Use
]
def is_ip_blocked(ip: str) -> bool:
"""
Checks if the IP address is in a blocked network.
"""
ip_addr = ipaddress.ip_address(ip)
return any(ip_addr in network for network in BLOCKED_IP_NETWORKS)
def validate_url(url: str, trusted_origins: list[str]) -> str:
"""
Validates the URL to prevent SSRF attacks by ensuring it does not point to a private
or untrusted IP address, unless whitelisted.
"""
url = url.strip().strip("/")
if not url.startswith(("http://", "https://")):
url = "http://" + url
parsed_url = urlparse(url)
hostname = parsed_url.hostname
if not hostname:
raise ValueError(f"Invalid URL: Unable to determine hostname from {url}")
if any(hostname == origin for origin in trusted_origins):
return url
# Resolve all IP addresses for the hostname
ip_addresses = {result[4][0] for result in socket.getaddrinfo(hostname, None)}
if not ip_addresses:
raise ValueError(f"Unable to resolve IP address for {hostname}")
# Check if all IP addresses are global
for ip in ip_addresses:
if is_ip_blocked(ip):
raise ValueError(
f"Access to private IP address at {hostname}: {ip} is not allowed."
)
return url
class Requests:
"""
A wrapper around the requests library that validates URLs before making requests.
"""
def __init__(
self,
trusted_origins: list[str] | None = None,
raise_for_status: bool = True,
extra_url_validator: Callable[[str], str] | None = None,
extra_headers: dict[str, str] | None = None,
):
self.trusted_origins = []
for url in trusted_origins or []:
hostname = urlparse(url).hostname
if not hostname:
raise ValueError(f"Invalid URL: Unable to determine hostname of {url}")
self.trusted_origins.append(hostname)
self.raise_for_status = raise_for_status
self.extra_url_validator = extra_url_validator
self.extra_headers = extra_headers
def request(
self, method, url, headers=None, allow_redirects=False, *args, **kwargs
) -> req.Response:
if self.extra_headers is not None:
headers = {**(headers or {}), **self.extra_headers}
url = validate_url(url, self.trusted_origins)
if self.extra_url_validator is not None:
url = self.extra_url_validator(url)
response = req.request(
method,
url,
headers=headers,
allow_redirects=allow_redirects,
*args,
**kwargs,
)
if self.raise_for_status:
response.raise_for_status()
return response
def get(self, url, *args, **kwargs) -> req.Response:
return self.request("GET", url, *args, **kwargs)
def post(self, url, *args, **kwargs) -> req.Response:
return self.request("POST", url, *args, **kwargs)
def put(self, url, *args, **kwargs) -> req.Response:
return self.request("PUT", url, *args, **kwargs)
def delete(self, url, *args, **kwargs) -> req.Response:
return self.request("DELETE", url, *args, **kwargs)
def head(self, url, *args, **kwargs) -> req.Response:
return self.request("HEAD", url, *args, **kwargs)
def options(self, url, *args, **kwargs) -> req.Response:
return self.request("OPTIONS", url, *args, **kwargs)
def patch(self, url, *args, **kwargs) -> req.Response:
return self.request("PATCH", url, *args, **kwargs)
requests = Requests(trusted_origins=Config().trust_endpoints_for_requests)

View File

@@ -11,7 +11,7 @@ from pydantic_settings import (
SettingsConfigDict,
)
from backend.util.data import get_config_path, get_data_path, get_secrets_path
from backend.util.data import get_data_path, get_secrets_path
T = TypeVar("T", bound=BaseSettings)
@@ -92,10 +92,6 @@ class Config(UpdateTrackingModel["Config"], BaseSettings):
# Add more configuration fields as needed
model_config = SettingsConfigDict(
json_file=[
get_config_path() / "config.default.json",
get_config_path() / "config.json",
],
env_file=".env",
extra="allow",
)
@@ -161,6 +157,11 @@ class Config(UpdateTrackingModel["Config"], BaseSettings):
description="Name of the event bus",
)
trust_endpoints_for_requests: List[str] = Field(
default_factory=list,
description="A whitelist of trusted internal endpoints for the backend to make requests to.",
)
backend_cors_allow_origins: List[str] = Field(default_factory=list)
@field_validator("backend_cors_allow_origins")
@@ -265,6 +266,7 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
replicate_api_key: str = Field(default="", description="Replicate API Key")
unreal_speech_api_key: str = Field(default="", description="Unreal Speech API Key")
ideogram_api_key: str = Field(default="", description="Ideogram API Key")
jina_api_key: str = Field(default="", description="Jina API Key")
# Add more secret fields as needed

View File

@@ -1,5 +0,0 @@
{
"num_graph_workers": 10,
"num_node_workers": 5,
"num_user_credits_refill": 1500
}

View File

@@ -0,0 +1,44 @@
-- CreateEnum
CREATE TYPE "APIKeyPermission" AS ENUM ('EXECUTE_GRAPH', 'READ_GRAPH', 'EXECUTE_BLOCK', 'READ_BLOCK');
-- CreateEnum
CREATE TYPE "APIKeyStatus" AS ENUM ('ACTIVE', 'REVOKED', 'SUSPENDED');
-- CreateTable
CREATE TABLE "APIKey" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"prefix" TEXT NOT NULL,
"postfix" TEXT NOT NULL,
"key" TEXT NOT NULL,
"status" "APIKeyStatus" NOT NULL DEFAULT 'ACTIVE',
"permissions" "APIKeyPermission"[],
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastUsedAt" TIMESTAMP(3),
"revokedAt" TIMESTAMP(3),
"description" TEXT,
"userId" TEXT NOT NULL,
CONSTRAINT "APIKey_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "APIKey_key_key" ON "APIKey"("key");
-- CreateIndex
CREATE INDEX "APIKey_key_idx" ON "APIKey"("key");
-- CreateIndex
CREATE INDEX "APIKey_prefix_idx" ON "APIKey"("prefix");
-- CreateIndex
CREATE INDEX "APIKey_userId_idx" ON "APIKey"("userId");
-- CreateIndex
CREATE INDEX "APIKey_status_idx" ON "APIKey"("status");
-- CreateIndex
CREATE INDEX "APIKey_userId_status_idx" ON "APIKey"("userId", "status");
-- AddForeignKey
ALTER TABLE "APIKey" ADD CONSTRAINT "APIKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -1079,17 +1079,17 @@ requests = ">=2.20.0,<3.0"
[[package]]
name = "gotrue"
version = "2.9.0"
version = "2.10.0"
description = "Python Client Library for Supabase Auth"
optional = false
python-versions = "<4.0,>=3.8"
python-versions = "<4.0,>=3.9"
files = [
{file = "gotrue-2.9.0-py3-none-any.whl", hash = "sha256:9a6448479329771752cb93be65bc95f06f17d9262e814a95d03b218cf5dce87a"},
{file = "gotrue-2.9.0.tar.gz", hash = "sha256:c50e75bd01b82a388eed6a921a1c373a7157fd405df2221a8532193a39df4159"},
{file = "gotrue-2.10.0-py3-none-any.whl", hash = "sha256:768e58207488e5184ffbdc4351b7280d913daf97962f4e9f2cca05c80004b042"},
{file = "gotrue-2.10.0.tar.gz", hash = "sha256:4edf4c251da3535f2b044e23deba221e848ca1210c17d0c7a9b19f79a1e3f3c0"},
]
[package.dependencies]
httpx = {version = ">=0.24,<0.28", extras = ["http2"]}
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
pydantic = ">=1.10,<3"
[[package]]
@@ -1796,13 +1796,13 @@ httpx = ">=0.27.0,<0.28.0"
[[package]]
name = "openai"
version = "1.54.1"
version = "1.54.3"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
files = [
{file = "openai-1.54.1-py3-none-any.whl", hash = "sha256:3cb49ccb6bfdc724ad01cc397d323ef8314fc7d45e19e9de2afdd6484a533324"},
{file = "openai-1.54.1.tar.gz", hash = "sha256:5b832bf82002ba8c4f6e5e25c1c0f5d468c22f043711544c716eaffdb30dd6f1"},
{file = "openai-1.54.3-py3-none-any.whl", hash = "sha256:f18dbaf09c50d70c4185b892a2a553f80681d1d866323a2da7f7be2f688615d5"},
{file = "openai-1.54.3.tar.gz", hash = "sha256:7511b74eeb894ac0b0253dc71f087a15d2e4d71d22d0088767205143d880cca6"},
]
[package.dependencies]
@@ -1965,13 +1965,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "poethepoet"
version = "0.29.0"
version = "0.30.0"
description = "A task runner that works well with poetry."
optional = false
python-versions = ">=3.8"
files = [
{file = "poethepoet-0.29.0-py3-none-any.whl", hash = "sha256:f8dfe55006dcfb5cf31bcb1904e1262e1c642a4502fee3688cbf1bddfe5c7601"},
{file = "poethepoet-0.29.0.tar.gz", hash = "sha256:676842302f2304a86b31ac56398dd672fae8471128d2086896393384dbafc095"},
{file = "poethepoet-0.30.0-py3-none-any.whl", hash = "sha256:bf875741407a98da9e96f2f2d0b2c4c34f56d89939a7f53a4b6b3a64b546ec4e"},
{file = "poethepoet-0.30.0.tar.gz", hash = "sha256:9f7ccda2d6525616ce989ca8ef973739fd668f50bef0b9d3631421d504d9ae4a"},
]
[package.dependencies]
@@ -1984,13 +1984,13 @@ poetry-plugin = ["poetry (>=1.0,<2.0)"]
[[package]]
name = "postgrest"
version = "0.17.2"
version = "0.18.0"
description = "PostgREST client for Python. This library provides an ORM interface to PostgREST."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "postgrest-0.17.2-py3-none-any.whl", hash = "sha256:f7c4f448e5a5e2d4c1dcf192edae9d1007c4261e9a6fb5116783a0046846ece2"},
{file = "postgrest-0.17.2.tar.gz", hash = "sha256:445cd4e4a191e279492549df0c4e827d32f9d01d0852599bb8a6efb0f07fcf78"},
{file = "postgrest-0.18.0-py3-none-any.whl", hash = "sha256:200baad0d23fee986b3a0ffd3e07bfe0cdd40e09760f11e8e13a6c0c2376d5fa"},
{file = "postgrest-0.18.0.tar.gz", hash = "sha256:29c1a94801a17eb9ad590189993fe5a7a6d8c1bfc11a3c9d0ce7ba146454ebb3"},
]
[package.dependencies]
@@ -2370,13 +2370,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyright"
version = "1.1.387"
version = "1.1.388"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
files = [
{file = "pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5"},
{file = "pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60"},
{file = "pyright-1.1.388-py3-none-any.whl", hash = "sha256:c7068e9f2c23539c6ac35fc9efac6c6c1b9aa5a0ce97a9a8a6cf0090d7cbf84c"},
{file = "pyright-1.1.388.tar.gz", hash = "sha256:0166d19b716b77fd2d9055de29f71d844874dbc6b9d3472ccd22df91db3dfa34"},
]
[package.dependencies]
@@ -2792,29 +2792,29 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.7.2"
version = "0.7.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
{file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
{file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
{file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
{file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
{file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
{file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
{file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
{file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
{file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
{file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
{file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
{file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
{file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
]
[[package]]
@@ -2933,19 +2933,18 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7
[[package]]
name = "storage3"
version = "0.8.2"
version = "0.9.0"
description = "Supabase Storage client for Python."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "storage3-0.8.2-py3-none-any.whl", hash = "sha256:f2e995b18c77a2a9265d1a33047d43e4d6abb11eb3ca5067959f68281c305de3"},
{file = "storage3-0.8.2.tar.gz", hash = "sha256:db05d3fe8fb73bd30c814c4c4749664f37a5dfc78b629e8c058ef558c2b89f5a"},
{file = "storage3-0.9.0-py3-none-any.whl", hash = "sha256:8b2fb91f0c61583a2f4eac74a8bae67e00d41ff38095c8a6cd3f2ce5e0ab76e7"},
{file = "storage3-0.9.0.tar.gz", hash = "sha256:e16697f60894c94e1d9df0d2e4af783c1b3f7dd08c9013d61978825c624188c4"},
]
[package.dependencies]
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
python-dateutil = ">=2.8.2,<3.0.0"
typing-extensions = ">=4.2.0,<5.0.0"
[[package]]
name = "strenum"
@@ -2965,32 +2964,32 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
[[package]]
name = "supabase"
version = "2.9.1"
version = "2.10.0"
description = "Supabase client for Python."
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "supabase-2.9.1-py3-none-any.whl", hash = "sha256:a96f857a465712cb551679c1df66ba772c834f861756ce4aa2aa4cb703f6aeb7"},
{file = "supabase-2.9.1.tar.gz", hash = "sha256:51fce39c9eb50573126dabb342541ec5e1f13e7476938768f4b0ccfdb8c522cd"},
{file = "supabase-2.10.0-py3-none-any.whl", hash = "sha256:183fb23c04528593f8f81c24ceb8178f3a56bff40fec7ed873b6c55ebc2e420a"},
{file = "supabase-2.10.0.tar.gz", hash = "sha256:9ac095f8947bf60780e67c0edcbab53e2db3f6f3f022329397b093500bf2607c"},
]
[package.dependencies]
gotrue = ">=2.9.0,<3.0.0"
gotrue = ">=2.10.0,<3.0.0"
httpx = ">=0.26,<0.28"
postgrest = ">=0.17.0,<0.18.0"
postgrest = ">=0.18,<0.19"
realtime = ">=2.0.0,<3.0.0"
storage3 = ">=0.8.0,<0.9.0"
supafunc = ">=0.6.0,<0.7.0"
storage3 = ">=0.9.0,<0.10.0"
supafunc = ">=0.7.0,<0.8.0"
[[package]]
name = "supafunc"
version = "0.6.2"
version = "0.7.0"
description = "Library for Supabase Functions"
optional = false
python-versions = "<4.0,>=3.9"
files = [
{file = "supafunc-0.6.2-py3-none-any.whl", hash = "sha256:101b30616b0a1ce8cf938eca1df362fa4cf1deacb0271f53ebbd674190fb0da5"},
{file = "supafunc-0.6.2.tar.gz", hash = "sha256:c7dfa20db7182f7fe4ae436e94e05c06cd7ed98d697fed75d68c7b9792822adc"},
{file = "supafunc-0.7.0-py3-none-any.whl", hash = "sha256:4160260dc02bdd906be1e2ffd7cb3ae8b74ae437c892bb475352b6a99d9ff8eb"},
{file = "supafunc-0.7.0.tar.gz", hash = "sha256:5b1c415fba1395740b2b4eedd1d786384bd58b98f6333a11ba7889820a48b6a7"},
]
[package.dependencies]
@@ -3677,4 +3676,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "b761f200e8ad7560321fca7bbefbe79377740952ace5dcbecf0371bb8aa16df1"
content-hash = "03327de6fcc1d05795769eb3724f4815140e7ce984907190939f5f019e37f5fc"

View File

@@ -26,7 +26,7 @@ jinja2 = "^3.1.4"
jsonref = "^1.1.0"
jsonschema = "^4.22.0"
ollama = "^0.3.0"
openai = "^1.54.1"
openai = "^1.54.3"
praw = "~7.8.1"
prisma = "^0.15.0"
psutil = "^6.1.0"
@@ -38,7 +38,7 @@ pytest-asyncio = "^0.24.0"
python-dotenv = "^1.0.1"
redis = "^5.2.0"
sentry-sdk = "2.18.0"
supabase = "^2.7.2"
supabase = "^2.10.0"
tenacity = "^9.0.0"
uvicorn = { extras = ["standard"], version = "^0.32.0" }
websockets = "^13.1"
@@ -48,12 +48,12 @@ replicate = "^1.0.3"
pinecone = "^5.3.1"
cryptography = "^43.0.3"
[tool.poetry.group.dev.dependencies]
poethepoet = "^0.29.0"
poethepoet = "^0.30.0"
httpx = "^0.27.0"
pytest-watcher = "^0.4.2"
requests = "^2.32.3"
ruff = "^0.7.2"
pyright = "^1.1.387"
ruff = "^0.7.3"
pyright = "^1.1.388"
isort = "^5.13.2"
black = "^24.10.0"
aiohappyeyeballs = "^2.4.3"

View File

@@ -27,6 +27,7 @@ model User {
AnalyticsDetails AnalyticsDetails[]
AnalyticsMetrics AnalyticsMetrics[]
UserBlockCredit UserBlockCredit[]
APIKeys APIKey[]
@@index([id])
@@index([email])
@@ -277,3 +278,42 @@ model UserBlockCredit {
@@id(name: "creditTransactionIdentifier", [transactionKey, userId])
}
enum APIKeyPermission {
EXECUTE_GRAPH // Can execute agent graphs
READ_GRAPH // Can get graph versions and details
EXECUTE_BLOCK // Can execute individual blocks
READ_BLOCK // Can get block information
}
model APIKey {
id String @id @default(uuid())
name String
prefix String // First 8 chars for identification
postfix String
key String @unique // Hashed key
status APIKeyStatus @default(ACTIVE)
permissions APIKeyPermission[]
createdAt DateTime @default(now())
lastUsedAt DateTime?
revokedAt DateTime?
description String?
// Relation to user
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([key])
@@index([prefix])
@@index([userId])
@@index([status])
@@index([userId, status])
}
enum APIKeyStatus {
ACTIVE
REVOKED
SUSPENDED
}

View File

@@ -72,6 +72,7 @@ async def test_send_execution_result(
graph_exec_id="test_exec_id",
node_exec_id="test_node_exec_id",
node_id="test_node_id",
block_id="test_block_id",
status=ExecutionStatus.COMPLETED,
input_data={"input1": "value1"},
output_data={"output1": ["result1"]},
@@ -102,6 +103,7 @@ async def test_send_execution_result_no_subscribers(
graph_exec_id="test_exec_id",
node_exec_id="test_node_exec_id",
node_id="test_node_id",
block_id="test_block_id",
status=ExecutionStatus.COMPLETED,
input_data={"input1": "value1"},
output_data={"output1": ["result1"]},

View File

@@ -0,0 +1,21 @@
import pytest
from backend.util.request import validate_url
def test_validate_url():
with pytest.raises(ValueError):
validate_url("localhost", [])
with pytest.raises(ValueError):
validate_url("192.168.1.1", [])
with pytest.raises(ValueError):
validate_url("127.0.0.1", [])
with pytest.raises(ValueError):
validate_url("0.0.0.0", [])
validate_url("google.com", [])
validate_url("github.com", [])
validate_url("http://github.com", [])

View File

@@ -23,7 +23,7 @@
],
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@next/third-parties": "^15.0.2",
"@next/third-parties": "^15.0.3",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
@@ -41,26 +41,26 @@
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@sentry/nextjs": "^8",
"@supabase/ssr": "^0.5.1",
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.46.1",
"@tanstack/react-table": "^8.20.5",
"@xyflow/react": "^12.3.4",
"@xyflow/react": "^12.3.5",
"ajv": "^8.17.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"cookie": "0.7.0",
"cookie": "1.0.1",
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"elliptic": "6.6.0",
"lucide-react": "^0.454.0",
"lucide-react": "^0.456.0",
"moment": "^2.30.1",
"next": "^14.2.13",
"next-themes": "^0.4.3",
"react": "^18",
"react-day-picker": "^9.2.1",
"react-day-picker": "^9.3.0",
"react-dom": "^18",
"react-hook-form": "^7.53.1",
"react-hook-form": "^7.53.2",
"react-icons": "^5.3.0",
"react-markdown": "^9.0.1",
"react-modal": "^3.16.1",
@@ -68,33 +68,33 @@
"recharts": "^2.13.3",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"uuid": "^11.0.2",
"uuid": "^11.0.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.2",
"@playwright/test": "^1.48.2",
"@storybook/addon-essentials": "^8.4.1",
"@storybook/addon-interactions": "^8.4.1",
"@storybook/addon-links": "^8.4.1",
"@storybook/addon-onboarding": "^8.4.1",
"@storybook/blocks": "^8.4.1",
"@storybook/nextjs": "^8.4.1",
"@storybook/addon-essentials": "^8.4.2",
"@storybook/addon-interactions": "^8.4.2",
"@storybook/addon-links": "^8.4.2",
"@storybook/addon-onboarding": "^8.4.2",
"@storybook/blocks": "^8.4.2",
"@storybook/nextjs": "^8.4.2",
"@storybook/react": "^8.3.5",
"@storybook/test": "^8.3.5",
"@storybook/test-runner": "^0.19.1",
"@types/node": "^22.8.7",
"@types/node": "^22.9.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-modal": "^3.16.3",
"concurrently": "^9.1.0",
"eslint": "^8",
"eslint-config-next": "15.0.2",
"eslint-config-next": "15.0.3",
"eslint-plugin-storybook": "^0.11.0",
"postcss": "^8",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"storybook": "^8.4.1",
"storybook": "^8.4.2",
"tailwindcss": "^3.4.14",
"typescript": "^5"
},

View File

@@ -93,6 +93,12 @@ export function CustomNode({
const isInitialSetup = useRef(true);
const flowContext = useContext(FlowContext);
if (data.uiType === BlockUIType.AGENT) {
// Display the graph's schema instead AgentExecutorBlock's schema.
data.inputSchema = data.hardcodedValues?.input_schema || {};
data.outputSchema = data.hardcodedValues?.output_schema || {};
}
if (!flowContext) {
throw new Error("FlowContext consumer must be inside FlowEditor component");
}
@@ -163,38 +169,6 @@ export function CustomNode({
if (!schema?.properties) return null;
let keys = Object.entries(schema.properties);
switch (nodeType) {
case BlockUIType.INPUT:
// For INPUT blocks, dont include connection handles
return keys.map(([propKey, propSchema]) => {
const isRequired = data.inputSchema.required?.includes(propKey);
const isConnected = isHandleConnected(propKey);
const isAdvanced = propSchema.advanced;
return (
(isRequired || isAdvancedOpen || !isAdvanced) && (
<div key={propKey} data-id={`input-handle-${propKey}`}>
<span className="text-m green mb-0 text-gray-900">
{propSchema.title || beautifyString(propKey)}
</span>
<div key={propKey}>
{!isConnected && (
<NodeGenericInputField
nodeId={id}
propKey={propKey}
propSchema={propSchema}
currentValue={getValue(propKey)}
connections={data.connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
errors={data.errors ?? {}}
displayName={propSchema.title || beautifyString(propKey)}
/>
)}
</div>
</div>
)
);
});
case BlockUIType.NOTE:
// For NOTE blocks, don't render any input handles
const [noteKey, noteSchema] = keys[0];
@@ -213,59 +187,25 @@ export function CustomNode({
</div>
);
case BlockUIType.OUTPUT:
// For OUTPUT blocks, only show the 'value' property
return keys.map(([propKey, propSchema]) => {
const isRequired = data.inputSchema.required?.includes(propKey);
const isConnected = isHandleConnected(propKey);
const isAdvanced = propSchema.advanced;
return (
(isRequired || isAdvancedOpen || !isAdvanced) && (
<div key={propKey} data-id={`output-handle-${propKey}`}>
{propKey !== "value" ? (
<span className="text-m green mb-0 text-gray-900">
{propSchema.title || beautifyString(propKey)}
</span>
) : (
<NodeHandle
keyName={propKey}
isConnected={isConnected}
isRequired={isRequired}
schema={propSchema}
side="left"
/>
)}
{!isConnected && (
<NodeGenericInputField
nodeId={id}
propKey={propKey}
propSchema={propSchema}
currentValue={getValue(propKey)}
connections={data.connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
errors={data.errors ?? {}}
displayName={propSchema.title || beautifyString(propKey)}
/>
)}
</div>
)
);
});
default:
const getInputPropKey = (key: string) =>
nodeType == BlockUIType.AGENT ? `data.${key}` : key;
return keys.map(([propKey, propSchema]) => {
const isRequired = data.inputSchema.required?.includes(propKey);
const isConnected = isHandleConnected(propKey);
const isAdvanced = propSchema.advanced;
const isConnectable =
// No input connection handles for credentials
propKey !== "credentials" &&
// No input connection handles on INPUT blocks
nodeType !== BlockUIType.INPUT &&
// For OUTPUT blocks, only show the 'value' (hides 'name') input connection handle
!(nodeType == BlockUIType.OUTPUT && propKey == "name");
return (
(isRequired || isAdvancedOpen || isConnected || !isAdvanced) && (
<div key={propKey} data-id={`input-handle-${propKey}`}>
{"credentials_provider" in propSchema ? (
<span className="text-m green mb-0 text-gray-900">
Credentials
</span>
) : (
{isConnectable ? (
<NodeHandle
keyName={propKey}
isConnected={isConnected}
@@ -273,13 +213,22 @@ export function CustomNode({
schema={propSchema}
side="left"
/>
) : (
<span
className="text-m green mb-0 text-gray-900"
title={propSchema.description}
>
{propKey == "credentials"
? "Credentials"
: propSchema.title || beautifyString(propKey)}
</span>
)}
{!isConnected && (
<NodeGenericInputField
nodeId={id}
propKey={propKey}
propKey={getInputPropKey(propKey)}
propSchema={propSchema}
currentValue={getValue(propKey)}
currentValue={getValue(getInputPropKey(propKey))}
connections={data.connections}
handleInputChange={handleInputChange}
handleInputClick={handleInputClick}
@@ -318,8 +267,6 @@ export function CustomNode({
current[lastKey.key] = value;
}
// console.log(`Updating hardcoded values for node ${id}:`, newValues);
if (!isInitialSetup.current) {
history.push({
type: "UPDATE_INPUT",

View File

@@ -27,11 +27,7 @@ import "@xyflow/react/dist/style.css";
import { CustomNode } from "./CustomNode";
import "./flow.css";
import { BlockUIType, Link } from "@/lib/autogpt-server-api";
import {
getTypeColor,
filterBlocksByType,
findNewlyAddedBlockCoordinates,
} from "@/lib/utils";
import { getTypeColor, findNewlyAddedBlockCoordinates } from "@/lib/utils";
import { history } from "./history";
import { CustomEdge } from "./CustomEdge";
import ConnectionLine from "./ConnectionLine";
@@ -48,7 +44,6 @@ import RunnerUIWrapper, {
} from "@/components/RunnerUIWrapper";
import PrimaryActionBar from "@/components/PrimaryActionButton";
import { useToast } from "@/components/ui/use-toast";
import { forceLoad } from "@sentry/nextjs";
import { useCopyPaste } from "../hooks/useCopyPaste";
// This is for the history, this is the minimum distance a block must move before it is logged
@@ -86,8 +81,6 @@ const FlowEditor: React.FC<{
setViewport,
} = useReactFlow<CustomNode, CustomEdge>();
const [nodeId, setNodeId] = useState<number>(1);
const [copiedNodes, setCopiedNodes] = useState<CustomNode[]>([]);
const [copiedEdges, setCopiedEdges] = useState<CustomEdge[]>([]);
const [isAnyModalOpen, setIsAnyModalOpen] = useState(false);
const [visualizeBeads, setVisualizeBeads] = useState<
"no" | "static" | "animate"
@@ -99,6 +92,7 @@ const FlowEditor: React.FC<{
setAgentDescription,
savedAgent,
availableNodes,
availableFlows,
getOutputType,
requestSave,
requestSaveAndRun,
@@ -417,7 +411,7 @@ const FlowEditor: React.FC<{
const { x, y, zoom } = useViewport();
const addNode = useCallback(
(blockId: string, nodeType: string) => {
(blockId: string, nodeType: string, hardcodedValues: any = {}) => {
const nodeSchema = availableNodes.find((node) => node.id === blockId);
if (!nodeSchema) {
console.error(`Schema not found for block ID: ${blockId}`);
@@ -462,7 +456,7 @@ const FlowEditor: React.FC<{
categories: nodeSchema.categories,
inputSchema: nodeSchema.inputSchema,
outputSchema: nodeSchema.outputSchema,
hardcodedValues: {},
hardcodedValues: hardcodedValues,
connections: [],
isOutputOpen: false,
block_id: blockId,
@@ -618,6 +612,7 @@ const FlowEditor: React.FC<{
pinBlocksPopover={pinBlocksPopover} // Pass the state to BlocksControl
blocks={availableNodes}
addBlock={addNode}
flows={availableFlows}
/>
}
botChildren={

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useCallback } from "react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
@@ -10,7 +10,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Block } from "@/lib/autogpt-server-api";
import { Block, BlockUIType } from "@/lib/autogpt-server-api";
import { MagnifyingGlassIcon, PlusIcon } from "@radix-ui/react-icons";
import { IconToyBrick } from "@/components/ui/icons";
import { getPrimaryCategoryColor } from "@/lib/utils";
@@ -19,11 +19,17 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { GraphMeta } from "@/lib/autogpt-server-api";
interface BlocksControlProps {
blocks: Block[];
addBlock: (id: string, name: string) => void;
addBlock: (
id: string,
name: string,
hardcodedValues: Record<string, any>,
) => void;
pinBlocksPopover: boolean;
flows: GraphMeta[];
}
/**
@@ -39,29 +45,42 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
blocks,
addBlock,
pinBlocksPopover,
flows,
}) => {
const blockList = blocks.sort((a, b) => a.name.localeCompare(b.name));
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [filteredBlocks, setFilteredBlocks] = useState<Block[]>(blockList);
const resetFilters = React.useCallback(() => {
setSearchQuery("");
setSelectedCategory(null);
setFilteredBlocks(blockList);
}, [blockList]);
const getFilteredBlockList = (): Block[] => {
const blockList = blocks
.filter((b) => b.uiType !== BlockUIType.AGENT)
.sort((a, b) => a.name.localeCompare(b.name));
const agentList = flows.map(
(flow) =>
({
id: "e189baac-8c20-45a1-94a7-55177ea42565", // TODO: fetch this programmatically.
name: flow.name,
description:
`Ver.${flow.version}` +
(flow.description ? ` | ${flow.description}` : ""),
categories: [{ category: "AGENT", description: "" }],
inputSchema: flow.input_schema,
outputSchema: flow.output_schema,
staticOutput: false,
uiType: BlockUIType.AGENT,
uiKey: flow.id,
costs: [],
hardcodedValues: {
graph_id: flow.id,
graph_version: flow.version,
input_schema: flow.input_schema,
output_schema: flow.output_schema,
},
}) as Block,
);
// Extract unique categories from blocks
const categories = Array.from(
new Set([
null,
...blocks.flatMap((block) => block.categories.map((cat) => cat.category)),
]),
);
React.useEffect(() => {
setFilteredBlocks(
blockList.filter(
return blockList
.concat(agentList)
.filter(
(block: Block) =>
(block.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
beautifyString(block.name)
@@ -69,9 +88,23 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
.includes(searchQuery.toLowerCase())) &&
(!selectedCategory ||
block.categories.some((cat) => cat.category === selectedCategory)),
),
);
}, [blockList, searchQuery, selectedCategory]);
);
};
const resetFilters = React.useCallback(() => {
setSearchQuery("");
setSelectedCategory(null);
}, []);
// Extract unique categories from blocks
const categories = Array.from(
new Set([
null,
...blocks
.flatMap((block) => block.categories.map((cat) => cat.category))
.sort(),
]),
);
return (
<Popover
@@ -150,12 +183,14 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
className="h-[60vh] w-fit w-full"
data-id="blocks-control-scroll-area"
>
{filteredBlocks.map((block) => (
{getFilteredBlockList().map((block) => (
<Card
key={block.id}
key={block.uiKey || block.id}
className="m-2 my-4 flex h-20 cursor-pointer shadow-none hover:shadow-lg"
data-id={`block-card-${block.id}`}
onClick={() => addBlock(block.id, block.name)}
onClick={() =>
addBlock(block.id, block.name, block?.hardcodedValues || {})
}
>
<div
className={`-ml-px h-full w-3 rounded-l-xl ${getPrimaryCategoryColor(block.categories)}`}

View File

@@ -53,7 +53,6 @@ const NodeObjectInputTree: FC<NodeObjectInputTreeProps> = ({
object ||= ("default" in schema ? schema.default : null) ?? {};
return (
<div className={cn(className, "w-full flex-col")}>
{displayName && <strong>{displayName}</strong>}
{Object.entries(schema.properties).map(([propKey, propSchema]) => {
const childKey = selfKey ? `${selfKey}.${propKey}` : propKey;
@@ -519,7 +518,6 @@ const NodeArrayInput: FC<{
typeof errors[selfKey] === "string" ? errors[selfKey] : undefined;
return (
<div className={cn(className, "flex flex-col")}>
{displayName && <strong>{displayName}</strong>}
{entries.map((entry: any, index: number) => {
const entryKey = `${selfKey}_$_${index}`;
const isConnected =
@@ -768,7 +766,7 @@ const NodeBooleanInput: FC<{
<div className={className}>
<div className="nodrag flex items-center">
<Switch
checked={value}
defaultChecked={value}
onCheckedChange={(v) => handleInputChange(selfKey, v)}
/>
<span className="ml-3">{displayName}</span>

View File

@@ -22,6 +22,12 @@ const isValidImageUrl = (url: string): boolean => {
return imageExtensions.test(cleanedUrl);
};
const isValidAudioUrl = (url: string): boolean => {
const audioExtensions = /\.(mp3|wav)$/i;
const cleanedUrl = url.split("?")[0];
return audioExtensions.test(cleanedUrl);
};
const VideoRenderer: React.FC<{ videoUrl: string }> = ({ videoUrl }) => {
const videoId = getYouTubeVideoId(videoUrl);
return (
@@ -58,6 +64,18 @@ const ImageRenderer: React.FC<{ imageUrl: string }> = ({ imageUrl }) => (
</div>
);
const AudioRenderer: React.FC<{ audioUrl: string }> = ({ audioUrl }) => (
<div className="w-full p-2">
<audio controls className="w-full">
<source
src={audioUrl}
type={`audio/${audioUrl.split(".").pop()?.toLowerCase()}`}
/>
Your browser does not support the audio element.
</audio>
</div>
);
const TextRenderer: React.FC<{ value: any; truncateLongData?: boolean }> = ({
value,
truncateLongData,
@@ -79,6 +97,8 @@ export const ContentRenderer: React.FC<{
return <VideoRenderer videoUrl={value} />;
} else if (isValidImageUrl(value)) {
return <ImageRenderer imageUrl={value} />;
} else if (isValidAudioUrl(value)) {
return <AudioRenderer audioUrl={value} />;
}
}
return <TextRenderer value={value} truncateLongData={truncateLongData} />;

View File

@@ -3,6 +3,7 @@ import { CustomNode } from "@/components/CustomNode";
import AutoGPTServerAPI, {
Block,
BlockIOSubSchema,
BlockUIType,
Graph,
Link,
NodeExecutionResult,
@@ -18,6 +19,7 @@ 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 { GraphMeta } from "@/lib/autogpt-server-api";
const ajv = new Ajv({ strict: false, allErrors: true });
@@ -36,6 +38,7 @@ export default function useAgentGraph(
const [agentDescription, setAgentDescription] = useState<string>("");
const [agentName, setAgentName] = useState<string>("");
const [availableNodes, setAvailableNodes] = useState<Block[]>([]);
const [availableFlows, setAvailableFlows] = useState<GraphMeta[]>([]);
const [updateQueue, setUpdateQueue] = useState<NodeExecutionResult[]>([]);
const processedUpdates = useRef<NodeExecutionResult[]>([]);
/**
@@ -93,12 +96,17 @@ export default function useAgentGraph(
};
}, [api]);
// Load available blocks
// Load available blocks & flows
useEffect(() => {
api
.getBlocks()
.then((blocks) => setAvailableNodes(blocks))
.catch();
api
.listGraphs()
.then((flows) => setAvailableFlows(flows))
.catch();
}, [api]);
//TODO to utils? repeated in Flow
@@ -118,7 +126,7 @@ export default function useAgentGraph(
const outputSchema = node.data.outputSchema;
if (!outputSchema) return "unknown";
const outputHandle = outputSchema.properties[handleId];
const outputHandle = outputSchema.properties[handleId] || {};
if (!("type" in outputHandle)) return "unknown";
return outputHandle.type;
},
@@ -137,6 +145,12 @@ export default function useAgentGraph(
const block = availableNodes.find(
(block) => block.id === node.block_id,
)!;
const flow =
block.uiType == BlockUIType.AGENT
? availableFlows.find(
(flow) => flow.id === node.input_default.graph_id,
)
: null;
const newNode: CustomNode = {
id: node.id,
type: "custom",
@@ -146,7 +160,7 @@ export default function useAgentGraph(
},
data: {
block_id: block.id,
blockType: block.name,
blockType: flow?.name || block.name,
blockCosts: block.costs,
categories: block.categories,
description: block.description,
@@ -200,7 +214,7 @@ export default function useAgentGraph(
return newNodes;
});
},
[availableNodes, formatEdgeID, getOutputType],
[availableNodes, availableFlows, formatEdgeID, getOutputType],
);
const getFrontendId = useCallback(
@@ -270,6 +284,7 @@ export default function useAgentGraph(
const updateNodesWithExecutionData = useCallback(
(executionData: NodeExecutionResult) => {
if (!executionData.node_id) return;
if (passDataToBeads) {
updateEdgeBeads(executionData);
}
@@ -415,10 +430,25 @@ export default function useAgentGraph(
if (saveRunRequest.state === "error") {
if (saveRunRequest.request === "save") {
console.error("Error saving agent");
toast({
variant: "destructive",
title: `Error saving agent`,
duration: 2000,
});
} else if (saveRunRequest.request === "run") {
toast({
variant: "destructive",
title: `Error saving&running agent`,
duration: 2000,
});
console.error(`Error saving&running agent`);
} else if (saveRunRequest.request === "stop") {
console.error(`Error stopping agent`);
toast({
variant: "destructive",
title: `Error stopping agent`,
duration: 2000,
});
}
// Reset request
setSaveRunRequest({
@@ -443,6 +473,11 @@ export default function useAgentGraph(
} else if (saveRunRequest.request === "run") {
if (!validateNodes()) {
console.error("Validation failed; aborting run");
toast({
title: "Invalid credentials or inputs",
variant: "destructive",
duration: 2000,
});
setSaveRunRequest({
request: "none",
state: "none",
@@ -652,8 +687,8 @@ export default function useAgentGraph(
const payload = {
id: savedAgent?.id!,
name: agentName || "Agent Name",
description: agentDescription || "Agent Description",
name: agentName || `New Agent ${new Date().toISOString()}`,
description: agentDescription || "",
nodes: formattedNodes,
links: links,
};
@@ -830,6 +865,7 @@ export default function useAgentGraph(
setAgentDescription,
savedAgent,
availableNodes,
availableFlows,
getOutputType,
requestSave,
requestSaveAndRun,

View File

@@ -25,7 +25,9 @@ export type Block = {
outputSchema: BlockIORootSchema;
staticOutput: boolean;
uiType: BlockUIType;
uiKey?: string;
costs: BlockCost[];
hardcodedValues: { [key: string]: any } | null;
};
export type BlockIORootSchema = {
@@ -190,6 +192,8 @@ export type GraphMeta = {
is_template: boolean;
name: string;
description: string;
input_schema: BlockIOObjectSubSchema;
output_schema: BlockIOObjectSubSchema;
};
export type GraphMetaWithRuns = GraphMeta & {
@@ -204,12 +208,19 @@ export type Graph = GraphMeta & {
export type GraphUpdateable = Omit<
Graph,
"version" | "is_active" | "is_template" | "links"
| "version"
| "is_active"
| "is_template"
| "links"
| "input_schema"
| "output_schema"
> & {
version?: number;
is_active?: boolean;
is_template?: boolean;
links: Array<LinkCreatable>;
input_schema?: BlockIOObjectSubSchema;
output_schema?: BlockIOObjectSubSchema;
};
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
@@ -299,6 +310,7 @@ export enum BlockUIType {
INPUT = "Input",
OUTPUT = "Output",
NOTE = "Note",
AGENT = "Agent",
}
export type AnalyticsMetrics = {

View File

@@ -211,6 +211,7 @@ export const categoryColorMap: Record<string, string> = {
OUTPUT: "bg-red-300",
LOGIC: "bg-teal-300",
DEVELOPER_TOOLS: "bg-fuchsia-300",
AGENT: "bg-lime-300",
};
export function getPrimaryCategoryColor(categories: Category[]): string {

View File

@@ -1657,10 +1657,10 @@
resolved "https://registry.npmjs.org/@next/env/-/env-14.2.13.tgz"
integrity sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==
"@next/eslint-plugin-next@15.0.2":
version "15.0.2"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.0.2.tgz#4aa6e4d3cd5d2d7239bf69e253471a92bd47ac67"
integrity sha512-R9Jc7T6Ge0txjmqpPwqD8vx6onQjynO9JT73ArCYiYPvSrwYXepH/UY/WdKDY8JPWJl72sAE4iGMHPeQ5xdEWg==
"@next/eslint-plugin-next@15.0.3":
version "15.0.3"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.0.3.tgz#ce953098036d462f6901e423cc6445fc165b78c4"
integrity sha512-3Ln/nHq2V+v8uIaxCR6YfYo7ceRgZNXfTd3yW1ukTaFbO+/I8jNakrjYWODvG9BuR2v5kgVtH/C8r0i11quOgw==
dependencies:
fast-glob "3.3.1"
@@ -1709,10 +1709,10 @@
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz#5a920eea82a58affa6146192586716cec6c87fed"
integrity sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==
"@next/third-parties@^15.0.2":
version "15.0.2"
resolved "https://registry.yarnpkg.com/@next/third-parties/-/third-parties-15.0.2.tgz#f25c4b58f5242d7918acdeb3b5fb0809309738c7"
integrity sha512-Ohlh0KKfag3Vrx+yuSMJ/fSoCVvRoVG9wRiz8jvYelmg+l0970d41VoGzF2UeKwh9s5qXVRDVqiN/mIeiJ4iLg==
"@next/third-parties@^15.0.3":
version "15.0.3"
resolved "https://registry.yarnpkg.com/@next/third-parties/-/third-parties-15.0.3.tgz#e29f3ebe67aced7d510866d69dd178443dd94f43"
integrity sha512-T2NkBXLcgRY0cmE7jb/dSMXNK9D+yv1k+n0uBxwMBS9SEtOhuMvxiUPQRj5x4cFnsei6JECloJg88koMprKw0A==
dependencies:
third-party-capital "1.0.20"
@@ -1810,13 +1810,13 @@
"@opentelemetry/instrumentation" "^0.54.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@opentelemetry/instrumentation-fastify@0.40.0":
version "0.40.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.40.0.tgz#0c57608ac202337d56b53338f1fc9369d224306b"
integrity sha512-74qj4nG3zPtU7g2x4sm2T4R3/pBMyrYstTsqSZwdlhQk1SD4l8OSY9sPRX1qkhfxOuW3U4KZQAV/Cymb3fB6hg==
"@opentelemetry/instrumentation-fastify@0.41.0":
version "0.41.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.41.0.tgz#5e1d00383756f3a8cc2ea4a9d15f9f7510cec571"
integrity sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==
dependencies:
"@opentelemetry/core" "^1.8.0"
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/instrumentation" "^0.54.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@opentelemetry/instrumentation-fs@0.16.0":
@@ -1834,12 +1834,12 @@
dependencies:
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/instrumentation-graphql@0.43.0":
version "0.43.0"
resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz"
integrity sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA==
"@opentelemetry/instrumentation-graphql@0.44.0":
version "0.44.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.44.0.tgz#6fce8e2f303d16810bf8a03148cad6e8e6119de1"
integrity sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==
dependencies:
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/instrumentation" "^0.54.0"
"@opentelemetry/instrumentation-hapi@0.41.0":
version "0.41.0"
@@ -1893,13 +1893,12 @@
dependencies:
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/instrumentation-mongodb@0.47.0":
version "0.47.0"
resolved "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz"
integrity sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==
"@opentelemetry/instrumentation-mongodb@0.48.0":
version "0.48.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.48.0.tgz#40fb8c705cb4bf8d8c5bf8752c60c5a0aaaaf617"
integrity sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==
dependencies:
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/sdk-metrics" "^1.9.1"
"@opentelemetry/instrumentation" "^0.54.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@opentelemetry/instrumentation-mongoose@0.42.0":
@@ -2014,14 +2013,6 @@
"@opentelemetry/core" "1.26.0"
"@opentelemetry/semantic-conventions" "1.27.0"
"@opentelemetry/sdk-metrics@^1.9.1":
version "1.26.0"
resolved "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz"
integrity sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==
dependencies:
"@opentelemetry/core" "1.26.0"
"@opentelemetry/resources" "1.26.0"
"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.26.0":
version "1.26.0"
resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz"
@@ -2546,61 +2537,61 @@
resolved "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.3.0.tgz"
integrity sha512-lHKK8M5CTcpFj2hZDB3wIjb0KAbEOgDmiJGDv1WBRfQgRm/a8/XMEkG/N1iM01xgbUDsPQwi42D+dFo1XPAKew==
"@sentry-internal/browser-utils@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.36.0.tgz#c6a1b6d7eb2ccfc2f9c189504d94b3996367ccbf"
integrity sha512-AVJ9GmQW7jYxaal6hjQnnktsDNype01ajVC4q1RyOn1SfzSnXg6mXwj4xm4ovuJV+aBI7fAZJ55vEX5ASuP0ZA==
"@sentry-internal/browser-utils@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz#374028d8e37047aeda14b226707e6601de65996e"
integrity sha512-OSR/V5GCsSCG7iapWtXCT/y22uo3HlawdEgfM1NIKk1mkP15UyGQtGEzZDdih2H+SNuX1mp9jQLTjr5FFp1A5w==
dependencies:
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry-internal/feedback@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.36.0.tgz#2cda799f921543522aa5cbf8f9c3300209b54825"
integrity sha512-aAMTm3uDBj8Ta7FwoohpLmJOpWzpWXvvtTbtmSgkeCtPJLUS8DZDCTZ9uCILUkpuYrv2savRUHsdPkxNjgL8FA==
"@sentry-internal/feedback@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.37.1.tgz#e2d5fc934ca3b4925a5f5d0e63549830a1cf147e"
integrity sha512-Se25NXbSapgS2S+JssR5YZ48b3OY4UGmAuBOafgnMW91LXMxRNWRbehZuNUmjjHwuywABMxjgu+Yp5uJDATX+g==
dependencies:
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry-internal/replay-canvas@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.36.0.tgz#557c8677f6c386cf79b5f8ab7485b31f4347a6cb"
integrity sha512-KJPLf+qYdrQdmouoAqIPZ2KeapIBlHWbzNdQqNxJFWLHFFjpLUtt0b+87ruvbA/q3NYy2fDwD7EB0tGS1RHBaA==
"@sentry-internal/replay-canvas@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.37.1.tgz#e8a5e350e486b16938b3dd99886be23b7b6eff18"
integrity sha512-1JLAaPtn1VL5vblB0BMELFV0D+KUm/iMGsrl4/JpRm0Ws5ESzQl33DhXVv1IX/ZAbx9i14EjR7MG9+Hj70tieQ==
dependencies:
"@sentry-internal/replay" "8.36.0"
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry-internal/replay" "8.37.1"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry-internal/replay@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.36.0.tgz#8f5dccab0bcc3429650a461c46a01085f2f8b782"
integrity sha512-lbic98GsSkDeinQDix54tBFEgHUlmBtO+HjXECk9jIE0vOzR4As20/s5ta46t1rKMLlnxOtJuT5jKXeUYogBUw==
"@sentry-internal/replay@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.37.1.tgz#6dc2e3955879f6e7ab830db1ddee54e0a9b401f3"
integrity sha512-E/Plhisk/pXJjOdOU12sg8m/APTXTA21iEniidP6jW3/+O0tD/H/UovEqa4odNTqxPMa798xHQSQNt5loYiaLA==
dependencies:
"@sentry-internal/browser-utils" "8.36.0"
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry-internal/browser-utils" "8.37.1"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/babel-plugin-component-annotate@2.22.6":
version "2.22.6"
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz#829d6caf2c95c1c46108336de4e1049e6521435e"
integrity sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ==
"@sentry/browser@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.36.0.tgz#da8977b37e56e5436cf3ea2c2e4a098f480ab115"
integrity sha512-bLrQNe+wD4DkCfB8OD5TF3Rr8KA2+aTo5wF3t3Bf6KVn8//iX1ia1hhtptYiRnbRkG/0AEPxlqL6XfPZYVPQ5A==
"@sentry/browser@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.37.1.tgz#2e6e4accc395ad9e6313e07b09415370c71e5874"
integrity sha512-5ym+iGiIpjIKKpMWi9S3/tXh9xneS+jqxwRTJqed3cb8i4ydfMAAP8sM3U8xMCWWABpWyIUW+fpewC0tkhE1aQ==
dependencies:
"@sentry-internal/browser-utils" "8.36.0"
"@sentry-internal/feedback" "8.36.0"
"@sentry-internal/replay" "8.36.0"
"@sentry-internal/replay-canvas" "8.36.0"
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry-internal/browser-utils" "8.37.1"
"@sentry-internal/feedback" "8.37.1"
"@sentry-internal/replay" "8.37.1"
"@sentry-internal/replay-canvas" "8.37.1"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/bundler-plugin-core@2.22.6":
version "2.22.6"
@@ -2670,61 +2661,61 @@
"@sentry/cli-win32-i686" "2.38.2"
"@sentry/cli-win32-x64" "2.38.2"
"@sentry/core@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.36.0.tgz#34276354f0cd2298803c2f8d86ba571473435ff1"
integrity sha512-cbq1WQyRqc/+YpPhjwQxfniUM3ZxmO3Pm1oisTB8dw6mlbgQfGD6aznEIjXWWJY6k6acewJlMUx09N7DnprtBw==
"@sentry/core@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.37.1.tgz#4bafb25c762ec8680874056f6160df276c1cc7c6"
integrity sha512-82csXby589iDupM3VgCHJeWZagUyEEaDnbFcoZ/Z91QX2Sjq8FcF5OsforoXjw09i0XTFqlkFAnQVpDBmMXcpQ==
dependencies:
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/nextjs@^8":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-8.36.0.tgz#2a4a3f320a7533d90bfe9f5dc8e5935ead43bf11"
integrity sha512-JLPK5ZSZdGyIPVx40qnOomHV04TB3p+HymYvfQwX7C7/Ocm8U0Q7v+0164IXHu0FVR/BfqhbE84s+mpAEnHvig==
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-8.37.1.tgz#e4e64b6388e8ab7e2d8e4cb7981775865bc3ada9"
integrity sha512-MMe+W1Jd/liYA47RU8qCFSYATgnVEAcFoREnbK2L4ooIDB2RP7jB8AX9LWD9ZWg9MduyQdDoFsI9OPIO3WmfuQ==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@opentelemetry/instrumentation-http" "0.53.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@rollup/plugin-commonjs" "26.0.1"
"@sentry-internal/browser-utils" "8.36.0"
"@sentry/core" "8.36.0"
"@sentry/node" "8.36.0"
"@sentry/opentelemetry" "8.36.0"
"@sentry/react" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/vercel-edge" "8.36.0"
"@sentry-internal/browser-utils" "8.37.1"
"@sentry/core" "8.37.1"
"@sentry/node" "8.37.1"
"@sentry/opentelemetry" "8.37.1"
"@sentry/react" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/vercel-edge" "8.37.1"
"@sentry/webpack-plugin" "2.22.6"
chalk "3.0.0"
resolve "1.22.8"
rollup "3.29.5"
stacktrace-parser "^0.1.10"
"@sentry/node@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.36.0.tgz#6b4b6714c6c925e353e7c7eff0e45f8675bb530e"
integrity sha512-2RRbSck90TGpVz8F3OaNbq5Q9RXgeRlq5leGWHU7NfQOl3LmkG+vkzTbOqPDPZLtiYcw5KQ3G5G+vybrDS6AGg==
"@sentry/node@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.37.1.tgz#d061bad391a53b3d20d3399d2a2c5466a6ec54f3"
integrity sha512-ACRZmqOBHRPKsyVhnDR4+RH1QQr7WMdH7RNl62VlKNZGLvraxW1CUqTSeNUFUuOwks3P6nozROSQs8VMSC/nVg==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@opentelemetry/context-async-hooks" "^1.25.1"
"@opentelemetry/core" "^1.25.1"
"@opentelemetry/instrumentation" "^0.53.0"
"@opentelemetry/instrumentation" "^0.54.0"
"@opentelemetry/instrumentation-amqplib" "^0.42.0"
"@opentelemetry/instrumentation-connect" "0.40.0"
"@opentelemetry/instrumentation-dataloader" "0.12.0"
"@opentelemetry/instrumentation-express" "0.44.0"
"@opentelemetry/instrumentation-fastify" "0.40.0"
"@opentelemetry/instrumentation-fastify" "0.41.0"
"@opentelemetry/instrumentation-fs" "0.16.0"
"@opentelemetry/instrumentation-generic-pool" "0.39.0"
"@opentelemetry/instrumentation-graphql" "0.43.0"
"@opentelemetry/instrumentation-graphql" "0.44.0"
"@opentelemetry/instrumentation-hapi" "0.41.0"
"@opentelemetry/instrumentation-http" "0.53.0"
"@opentelemetry/instrumentation-ioredis" "0.43.0"
"@opentelemetry/instrumentation-kafkajs" "0.4.0"
"@opentelemetry/instrumentation-koa" "0.43.0"
"@opentelemetry/instrumentation-lru-memoizer" "0.40.0"
"@opentelemetry/instrumentation-mongodb" "0.47.0"
"@opentelemetry/instrumentation-mongodb" "0.48.0"
"@opentelemetry/instrumentation-mongoose" "0.42.0"
"@opentelemetry/instrumentation-mysql" "0.41.0"
"@opentelemetry/instrumentation-mysql2" "0.41.0"
@@ -2736,53 +2727,53 @@
"@opentelemetry/sdk-trace-base" "^1.26.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@prisma/instrumentation" "5.19.1"
"@sentry/core" "8.36.0"
"@sentry/opentelemetry" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/core" "8.37.1"
"@sentry/opentelemetry" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
import-in-the-middle "^1.11.2"
"@sentry/opentelemetry@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.36.0.tgz#4e4330fa67cfeb7f2e23d4e078659ac61806b3b0"
integrity sha512-pMKMphH0j1Mh8zknLWEEUaaaxeYn76rniGOxKLoQVk1pCUhhzkFEJdxKC41aR8yin/uN8X3CGWQb9vp/przwSg==
"@sentry/opentelemetry@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.37.1.tgz#366a86edf0c2d080e30eaddca367cd614a0ce850"
integrity sha512-P/Rp7R+qNiRYz9qtVMV12YL9CIrZjzXWGVUBZjJayHu37jdvMowCol5G850QPYy0E2O0AQnFtxBno2yeURn8QQ==
dependencies:
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/react@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.36.0.tgz#cb08298bacaddc585e8b655e7d859299cf597254"
integrity sha512-YIJZUx7Q5aulK034cRri0p/7MeP3tdLfdP6vMJMwrVlqoWQI9gKZXikmLIqHUQegZdMRYX5tr03gTWJu3dhYwg==
"@sentry/react@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-8.37.1.tgz#25ba2703b79436c9154e6f287959a8a3c040e8cb"
integrity sha512-HanDqBFTgIUhUsYztAHhSti+sEhQ8YopAymXgnpqkJ7j1PLHXZgQAre6M4Uvixu28WS5MDHC1onnAIBDgYRDYw==
dependencies:
"@sentry/browser" "8.36.0"
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/browser" "8.37.1"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
hoist-non-react-statics "^3.3.2"
"@sentry/types@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.36.0.tgz#b58397eb672d896b65b06103feb59dba74da9d39"
integrity sha512-K1pVFfdGHw115RzGHpwSOqoEPeayn4N1F9IfM0kxrYpQSbFT1X29eak88GBfC8gPiLEF0iFGlSaQ4ERmF7oRcA==
"@sentry/types@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.37.1.tgz#e92a7d346cfa29116568f4ffb58f65caedee0149"
integrity sha512-ryMOTROLSLINKFEbHWvi7GigNrsQhsaScw2NddybJGztJQ5UhxIGESnxGxWCufBmWFDwd7+5u0jDPCVUJybp7w==
"@sentry/utils@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.36.0.tgz#e733042ae231fdeeafe6970e49283dcd9ac9700f"
integrity sha512-oJ3EDPj0I00z+AwC3EWBpSidXYUoKW0Id8MfMQP5Hflniz3gif7UEReblT+FJgPEVo6+6uNzAncY0MuNMxmDKQ==
"@sentry/utils@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.37.1.tgz#6e020cd222d56d79953ea9d4630d91b3e323ceda"
integrity sha512-Qtn2IfpII12K17txG/ZtTci35XYjYi4CxbQ3j7nXY7toGv/+MqPXwV5q2i9g94XaSXlE5Wy9/hoCZoZpZs/djA==
dependencies:
"@sentry/types" "8.36.0"
"@sentry/types" "8.37.1"
"@sentry/vercel-edge@8.36.0":
version "8.36.0"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-8.36.0.tgz#d5767337762bbbaef4e502ee078ab920f2663bc0"
integrity sha512-5zSw+5JniztTkiOXU4pUXypD9opPVf91Gd71/khrFfDbDBT8gF/LlClp21ACt/c3jc1Bbk79j+lyYEXVe8/jIg==
"@sentry/vercel-edge@8.37.1":
version "8.37.1"
resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-8.37.1.tgz#cbd4690f420cb2696b41db40bd3238fbdfc07ce9"
integrity sha512-LBf1UFNermpDtV+n5tsOJgtc6b+9/uLsffvq64ktnx9x+Pz2/3sFAHauikB/fwmo0MLxYk9AIng5b2QL5+uv4Q==
dependencies:
"@opentelemetry/api" "^1.9.0"
"@sentry/core" "8.36.0"
"@sentry/types" "8.36.0"
"@sentry/utils" "8.36.0"
"@sentry/core" "8.37.1"
"@sentry/types" "8.37.1"
"@sentry/utils" "8.37.1"
"@sentry/webpack-plugin@2.22.6":
version "2.22.6"
@@ -2829,10 +2820,10 @@
dependencies:
"@sinonjs/commons" "^3.0.0"
"@storybook/addon-actions@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.4.1.tgz#1e0470c5c8fd53aacc7f976bbecfb2f610d9a195"
integrity sha512-D6KohTIA4JCHNol1X7Whp4LpOVU4cS5FfyOorwYo/WIzpHrUYc4Pw/+ex6DOmU/kgrk14mr8d9obVehKW7iNtA==
"@storybook/addon-actions@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.4.2.tgz#3aead1b324ff78144f004f22aa6784b1c1d8a13b"
integrity sha512-+hA200XN5aeA4T3jq8IifQq6Y+9FyNQ0Q+blM1L0Tl7WLzBc7B1kHQnKvhSj5pvMSBWc/Q/kY7Ev5t9gdOu13g==
dependencies:
"@storybook/global" "^5.0.0"
"@types/uuid" "^9.0.1"
@@ -2840,130 +2831,130 @@
polished "^4.2.2"
uuid "^9.0.0"
"@storybook/addon-backgrounds@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.4.1.tgz#daf01a4e59097ad53355e72b61075aa7d5a2cc0e"
integrity sha512-DIT1E9R9Sds8KTC+0m2X5cVa8hTNcKY1XKYTI9QdzQvdZzOt+K93AJqq2x8k5glingqUVpB6v2fSDmCUXp4+4g==
"@storybook/addon-backgrounds@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.4.2.tgz#56adb5772df3b9c0625aaca63ac3d867c952680c"
integrity sha512-s4uag5VKuk8q2MSnuNS7Sv+v1/mykzGPXe/zZRW2ammtkdHp8Uy78eQS2G0aiG02chXCX+qQgWMyy5QItDcTFQ==
dependencies:
"@storybook/global" "^5.0.0"
memoizerific "^1.11.3"
ts-dedent "^2.0.0"
"@storybook/addon-controls@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.4.1.tgz#1c051b0b3c20273717f9f581725fd98ca81660aa"
integrity sha512-3ahbYdDx7iFUd4X1KelMSuPqVnladc0bH4m6DQZyN+wkRxdRlOD6iOGuOe2qi1Gv0b2VuVAt253i75tK/TPNLw==
"@storybook/addon-controls@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.4.2.tgz#441320611e5f3aff7bdc5c740e0925b9728bd137"
integrity sha512-raCbHEj1xl4F3wKH6IdfEXNRaxKpY4QGhjSTE8Pte5iJSVhKG86taLqqRr+4dC7H1/LVMPU1XCGV4mkgDGtyxQ==
dependencies:
"@storybook/global" "^5.0.0"
dequal "^2.0.2"
ts-dedent "^2.0.0"
"@storybook/addon-docs@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.4.1.tgz#a53b32dd6482a8196038330ff06d0b9bcdef0019"
integrity sha512-yPD/NssJf7pMJzaKvma02C6yX8ykPVnEjhRbNYcBNM8s8g/cT5JkROvIB+FOb4T81yhdfbGg9bGkpAXGX270IQ==
"@storybook/addon-docs@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.4.2.tgz#43533b43bbaa8662bfcc69c343444a703d434a69"
integrity sha512-jIpykha7hv2Inlrq31ZoYg2QhuCuvcO+Q+uvhT45RDTB+2US/fg3rJINKlw2Djq8RPPOXvty5W0yvE6CrWKhnQ==
dependencies:
"@mdx-js/react" "^3.0.0"
"@storybook/blocks" "8.4.1"
"@storybook/csf-plugin" "8.4.1"
"@storybook/react-dom-shim" "8.4.1"
"@storybook/blocks" "8.4.2"
"@storybook/csf-plugin" "8.4.2"
"@storybook/react-dom-shim" "8.4.2"
react "^16.8.0 || ^17.0.0 || ^18.0.0"
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0"
ts-dedent "^2.0.0"
"@storybook/addon-essentials@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.4.1.tgz#98329606098c4b7fcb22a3968acba5fc539c821a"
integrity sha512-Hmb5fpVzQgyCacDtHeE7HJqIfolzeOnedsLyJVYVpKns/uOWXqpDuU8Fc0s3yTjr1QPIRKtbqV1STxoyXj2how==
"@storybook/addon-essentials@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.4.2.tgz#c633cb7eee48e2c6c5dbdc7cadebdf8191adf78c"
integrity sha512-+/vfPrXM/GWU3Kbrg92PepwAZr7lOeulTTYF4THK0CL3DfUUlkGNpBPLP5PtjCuIkVrTCjXiIEdVWk47d5m2+w==
dependencies:
"@storybook/addon-actions" "8.4.1"
"@storybook/addon-backgrounds" "8.4.1"
"@storybook/addon-controls" "8.4.1"
"@storybook/addon-docs" "8.4.1"
"@storybook/addon-highlight" "8.4.1"
"@storybook/addon-measure" "8.4.1"
"@storybook/addon-outline" "8.4.1"
"@storybook/addon-toolbars" "8.4.1"
"@storybook/addon-viewport" "8.4.1"
"@storybook/addon-actions" "8.4.2"
"@storybook/addon-backgrounds" "8.4.2"
"@storybook/addon-controls" "8.4.2"
"@storybook/addon-docs" "8.4.2"
"@storybook/addon-highlight" "8.4.2"
"@storybook/addon-measure" "8.4.2"
"@storybook/addon-outline" "8.4.2"
"@storybook/addon-toolbars" "8.4.2"
"@storybook/addon-viewport" "8.4.2"
ts-dedent "^2.0.0"
"@storybook/addon-highlight@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.4.1.tgz#89aa1b2dba43a351355f98930274f8828f6d6a75"
integrity sha512-BBkUd6+i7lUEWZwoJDlUIwrs7EXkk+EoREUi27iiA1Lilw+NNhoC3kcBmj3+MccjRyeMeIWAgYyXF5qeB2s/JQ==
"@storybook/addon-highlight@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.4.2.tgz#77fae7df40e178d33ff8e0bcf34282768d34dc5a"
integrity sha512-vTtwp7nyJ09SXrsMnH+pukCjHjRMjQXgHZHxvbrv09uoH8ldQMv9B7u+X+9Wcy/jYSKFz/ng7pWo4b4a2oXHkg==
dependencies:
"@storybook/global" "^5.0.0"
"@storybook/addon-interactions@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.4.1.tgz#a7d7b13e6c9424c818756babd3ee935a03cb0fd2"
integrity sha512-rMxKehtQogV6Scjb/oqMFM0Mwn8NJRuGFDRJE3TBijNSJ2HPJms+xXp8KVZJengadlsF5HFwQBbnZzIeFDQRLw==
"@storybook/addon-interactions@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.4.2.tgz#1808b6961ec5422347bcc2fe10ba15dfc921e620"
integrity sha512-+/NTENTApeOcONgFNQ6Olbk0GH3pTDG3w0eh00slCB+2agD1BcVKg8SSlHQV0lQF1cK3vWL/X3jeaxdFLYOjjg==
dependencies:
"@storybook/global" "^5.0.0"
"@storybook/instrumenter" "8.4.1"
"@storybook/test" "8.4.1"
"@storybook/instrumenter" "8.4.2"
"@storybook/test" "8.4.2"
polished "^4.2.2"
ts-dedent "^2.2.0"
"@storybook/addon-links@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.4.1.tgz#68dc9ebcb9f3e212fdf382fa618ecdba937325d0"
integrity sha512-wg83rNKo6mq5apV7f1qMn4q8xZ8wVx/42EEWxTOmnM37Q5kXltEBu+rUyBpPNDU8zBuXr/MRKIhK5h2k4WfWcg==
"@storybook/addon-links@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.4.2.tgz#facbfe343579ba76e391cd261862eeb29b44b5ab"
integrity sha512-8nncReA/drR2cyAcUz484FIv+MXbyCQxYrA6yfWHthZfGu+vMIETvhh+eP4OpluVnxySoQ+hCVK/V8G2jcyAZg==
dependencies:
"@storybook/csf" "^0.1.11"
"@storybook/global" "^5.0.0"
ts-dedent "^2.0.0"
"@storybook/addon-measure@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.4.1.tgz#18a8f1286bb1c2f50eed64790d6ee09ef0a15f9d"
integrity sha512-Pg1ROj29hKt7grL/HmbIJ10WrkZf1Unx35SsP373bkPQ1ggYi9oxGqtfNchTF2zCb1xUpIikLYSJgkwdjqWxhA==
"@storybook/addon-measure@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.4.2.tgz#43b24d3246502e34b3f26780e380f143bd057fac"
integrity sha512-z+j6xQwcUBSpgzl1XDU+xU4YYgLraLMljECW7NvRNyJ/PYixvol8R3wtzWbr+CBpxmvbXjEJCPlF+EjF9/mBWQ==
dependencies:
"@storybook/global" "^5.0.0"
tiny-invariant "^1.3.1"
"@storybook/addon-onboarding@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.4.1.tgz#d80f4ceddd486d7ec0525639577c2f5166060898"
integrity sha512-xcMCQ2HPvl8O/NHt2V5T1hDlzitRGI5ATSrfo7o7JjcsAgFNSNtcFn7YU8M5SIpHd5u4v24ubE/pMrsskpp/+Q==
"@storybook/addon-onboarding@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.4.2.tgz#1e56f9ac658a921d129d1a3a478ff4643a804902"
integrity sha512-zWzOyRASnIPt2AcaEl1KhI+aOaKDuoIcNB7u1GoABj0YM+V9d6o3lvcsmOAQG5pgwgFyqyOnLwpTfvRSEyzGFA==
dependencies:
react-confetti "^6.1.0"
"@storybook/addon-outline@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.4.1.tgz#fecde4f8a03bf70f416d7e8caa01d3fb4ca26023"
integrity sha512-LPZ0gGHfbru66Lkw1whnc3F/r1hfnoORBoF98Hp+cjH34gR4t8te6xq5qSiupRUULGdSLdBRs/4EGRBeELfVjQ==
"@storybook/addon-outline@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.4.2.tgz#cbf0f8fbb5c9a0a0a00a7ffdc67a823eeef05def"
integrity sha512-oTMlPEyT4CBqzcQbfemoJzJ6yzeRAmvrAx9ssaBcnQQRsKxo0D2Ri/Jmm6SNcR0yBHxYRkvIH+2phLw8aiflCQ==
dependencies:
"@storybook/global" "^5.0.0"
ts-dedent "^2.0.0"
"@storybook/addon-toolbars@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.4.1.tgz#a8dd4e57e071916984c6fb19cd37be47cddffd7c"
integrity sha512-yrzX6BFeJM5KFY0+ZAYfRax2QgWi2e5vF6yPz+MGIPr4nhHay0wTkOHhkBhIPBjQO9x0vqc7MS2EBDydCBWqlg==
"@storybook/addon-toolbars@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.4.2.tgz#0662c884ebc5c09b369644fc0f8ee3dca92ab1e9"
integrity sha512-DidzW/NQS224niMJIjcJI2ls83emqygUcS9GYNGgdc5Xwro/TPgGYOXP2qnXgYUxXQTHbrxmIbHdEehxC7CcYQ==
"@storybook/addon-viewport@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.4.1.tgz#f46e52d92a10ca880ff751401b1c0c5f8830a378"
integrity sha512-O6DcuUfXQTytjl7mj4ld4ZX9x2pUUWKUx1TxiuMuH0EKb612RyYcdpXpDQQwsIzLV/f2BOetk9jmO2/MymfbWQ==
"@storybook/addon-viewport@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.4.2.tgz#cc7014c9a64046de574334767936ee361e6f28c0"
integrity sha512-qVQ2UaxCNsUSFHnAAAizNPIJ/QwfMg7p5bBdpYROTZXJe+bxVp0rFzZmQgHZ3/sn+lzE4ItM4QEfxkfQUWi1ag==
dependencies:
memoizerific "^1.11.3"
"@storybook/blocks@8.4.1", "@storybook/blocks@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.4.1.tgz#6683ec554f92be5cc2990c0cff870c60d888c515"
integrity sha512-C4w5T5fhg0iONXozHQ1bh9im2Lr1BiY7Bj/9XoFjkc5YeCzxlMpujFA6Nmo4ToUFW90QbvKN7/QVhbrtY9O1Jg==
"@storybook/blocks@8.4.2", "@storybook/blocks@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.4.2.tgz#69f4458e4aeab1265ae6a304052c5239a0cb82da"
integrity sha512-yAAvmOWaD8gIrepOxCh/RxQqd/1xZIwd/V+gsvAhW/thawN+SpI+zK63gmcqAPLX84hJ3Dh5pegRk0SoHNuDVA==
dependencies:
"@storybook/csf" "^0.1.11"
"@storybook/icons" "^1.2.12"
ts-dedent "^2.0.0"
"@storybook/builder-webpack5@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.4.1.tgz#5406600591b4838cacc046fbbdc33b46f2d92be0"
integrity sha512-rqSJcxcYiQyceNFSrT9qnI6hrW4/petb1n+oN8nG5HrRsl0zxOVzamMVyNzZxrAMKvq+VMJtLe1rQi8FnJNunw==
"@storybook/builder-webpack5@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.4.2.tgz#ea2dce291f84c40e6c977c7dcdc6b9ffaab42aa8"
integrity sha512-Pqa0/sqqEujzcvs+/Cwf/5qRLC+atmceROCFokMOgpIaorTXlbmiQdJ2dBhMFNugLvXfL7dVQBjBfiuzhsQ57g==
dependencies:
"@storybook/core-webpack" "8.4.1"
"@storybook/core-webpack" "8.4.2"
"@types/node" "^22.0.0"
"@types/semver" "^7.3.4"
browser-assert "^1.2.1"
@@ -2989,28 +2980,28 @@
webpack-hot-middleware "^2.25.1"
webpack-virtual-modules "^0.6.0"
"@storybook/components@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.4.1.tgz#90b92e2a187af2e51bf6e8ad7e1c8b9c718a57ad"
integrity sha512-bMPclbBhrWxhFlwqrC/h4fPLl05ouoi5D8SkQTHjeVxWN9eDnMVi76xM0YDct302Z3f0x5S3plIulp+4XRxrvg==
"@storybook/components@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.4.2.tgz#e9e7d5dfaef3e36a2654c6bfbd79aa5a4f307a20"
integrity sha512-+W59oF7D73LAxLNmCfFrfs98cH9pyNHK9HlJoO5/lKbK4IdWhhOoqUR/AJ3ueksoLuetFat4DxyE8SN1H4Bvrg==
"@storybook/core-common@^8.0.0":
version "8.3.5"
resolved "https://registry.npmjs.org/@storybook/core-common/-/core-common-8.3.5.tgz"
integrity sha512-Dz91pcUH4mGgKRyo5AKiD6bhjC511d7J30SmMs5lgQl7nJWlepqon7Qhy+SzsEWTWtFTgRGPs//lKTmEaVT9ug==
"@storybook/core-webpack@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.4.1.tgz#28682704e33545874991bf5a1d84b644aa14153a"
integrity sha512-TptbDGaj9a8wJMF4g+C8t02CXl4BSd0BA/qGWBvzn3j4FJqeQ/m8elOXLYZrPbQKI6PjP0J4ayHkXdX2h0/tUw==
"@storybook/core-webpack@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.4.2.tgz#989c109c6cae7542b269ef9b4351138f1350b1c0"
integrity sha512-bzGvzrLK/oDE9YlKayDEplcECURSa1oRkvV7rxI2sOTNfwuoxHJapvxFxazEKAHMVeSwfWDf4uKK0XeG2R/arA==
dependencies:
"@types/node" "^22.0.0"
ts-dedent "^2.0.0"
"@storybook/core@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.4.1.tgz#e6094f3c7cc2f0c81d1c1195c91a7230641ba9eb"
integrity sha512-q3Q4OFBj7MHHbIFYk/Beejlqv5j7CC3+VWhGcr0TK3SGvdCIZ7EliYuc5JIOgDlEPsnTIk+lkgWI4LAA9mLzSw==
"@storybook/core@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.4.2.tgz#1e591fc6efef30e4e4fde4f266ca0cc9e756e516"
integrity sha512-hF8GWoUZTjwwuV5j4OLhMHZtZQL/NYcVUBReC2Ba06c8PkFIKqKZwATr1zKd301gQ5Qwcn9WgmZxJTMgdKQtOg==
dependencies:
"@storybook/csf" "^0.1.11"
better-opn "^3.0.2"
@@ -3024,10 +3015,10 @@
util "^0.12.5"
ws "^8.2.3"
"@storybook/csf-plugin@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.4.1.tgz#09b40718a752af3252e3ecced267445d9bdc110b"
integrity sha512-MdQkyq6mJ31lBsWCG9VNtx8O0oLSc5h4kvWDPyIP6Dn58K0Hv2z9qvxxSvtFjXA7ES9X+ivjorTke1kearifhg==
"@storybook/csf-plugin@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.4.2.tgz#3d098179c6ece8f4a053fdb258981cc6b467a1cb"
integrity sha512-1f0t6W5xbC1sSAHHs3uXYPIQs2NXAEtIGqn6X9i3xbbub6hDS8PF8BIm7dOjQ8dZOPp7d9ltR64V5CoLlsOigA==
dependencies:
unplugin "^1.3.1"
@@ -3053,23 +3044,23 @@
resolved "https://registry.npmjs.org/@storybook/icons/-/icons-1.2.12.tgz"
integrity sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==
"@storybook/instrumenter@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.4.1.tgz#cdb9e3476410a2acfe4b14da093810ed491b598d"
integrity sha512-MgrhrLVW78jqno+Dh9h9Es06Ja3867TlrIUd8B3K3U1hsCFUQuFKXJBuGjNJF8U0QJY/aSIRnAgUBurHdVkPcw==
"@storybook/instrumenter@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.4.2.tgz#6599abf2b698328659eb0cd5568d553f8d649776"
integrity sha512-gPYCZ/0O6gRLI3zmenu2N6QtKzxDZFdT2xf4RWcNUSZyp28RZkRCIgKFMt3fTmvE0yMzAjQyRSkBdrONjQ44HA==
dependencies:
"@storybook/global" "^5.0.0"
"@vitest/utils" "^2.1.1"
"@storybook/manager-api@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.4.1.tgz#7f625d3eef1dfb35102ee1d73e334aacda2a09fe"
integrity sha512-7hb2k4zsp6lREGZbQ85QOlsC8EIMZXuY9Pg12VUgaZd+LmLjLuaqtrxRz3SwIgIWsRpFun9AHO0X37DmYNGTSw==
"@storybook/manager-api@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.4.2.tgz#6bf972accfa6339034b50a7338654ad433aac6d1"
integrity sha512-rhPc4cgQDKDH8NUyRh/ZaJW7QIhR/PO5MNX4xc+vz71sM2nO7ONA/FrgLtCuu4SULdwilEPvGefYvLK0dE+Caw==
"@storybook/nextjs@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.4.1.tgz#106037a9cffeb08c62d35c519ddac5562b212233"
integrity sha512-SOEI8qOY+yLsRsvjokSevqtA+E+cXHDBObwPUJKmRxIAEwbP1uJEFnbvpZULs1pQl1gYsZQxEndbkTmM5pwtoQ==
"@storybook/nextjs@^8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.4.2.tgz#8862b7a3c76e394b35c7107d406c22f6d3dfdb9a"
integrity sha512-HySwS9zfenurk+O+SX9gKskotkHo8mFRBKAIlEROIWi7iipp5GCVPyqb8gFWjvN81dKfEIAZs+fB/7ySulJ4rg==
dependencies:
"@babel/core" "^7.24.4"
"@babel/plugin-syntax-bigint" "^7.8.3"
@@ -3085,10 +3076,10 @@
"@babel/preset-typescript" "^7.24.1"
"@babel/runtime" "^7.24.4"
"@pmmmwh/react-refresh-webpack-plugin" "^0.5.11"
"@storybook/builder-webpack5" "8.4.1"
"@storybook/preset-react-webpack" "8.4.1"
"@storybook/react" "8.4.1"
"@storybook/test" "8.4.1"
"@storybook/builder-webpack5" "8.4.2"
"@storybook/preset-react-webpack" "8.4.2"
"@storybook/react" "8.4.2"
"@storybook/test" "8.4.2"
"@types/node" "^22.0.0"
"@types/semver" "^7.3.4"
babel-loader "^9.1.3"
@@ -3112,13 +3103,13 @@
optionalDependencies:
sharp "^0.33.3"
"@storybook/preset-react-webpack@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.4.1.tgz#b5409acf42ae9acfa00eece104de0937ce281d61"
integrity sha512-Cm+u3/avHdoneEFHnvFRMPAElWtxyyOkcVsWHkM0rVhj7bxkzOyrBrenm1GiB8NamRosumsEnhREYFo2lthU2A==
"@storybook/preset-react-webpack@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.4.2.tgz#7c67cbbfe54131019931732558dea8b838018592"
integrity sha512-Gt9hQRo1ythGFzATNV4WgQDlMDzBgiq7ks+YkW2/Xu5ZkrRrM/gK75fhmbICrknZl2pPPfNFXlECPWKAeTmwFA==
dependencies:
"@storybook/core-webpack" "8.4.1"
"@storybook/react" "8.4.1"
"@storybook/core-webpack" "8.4.2"
"@storybook/react" "8.4.2"
"@storybook/react-docgen-typescript-plugin" "1.0.6--canary.9.0c3f3b7.0"
"@types/node" "^22.0.0"
"@types/semver" "^7.3.4"
@@ -3130,10 +3121,10 @@
tsconfig-paths "^4.2.0"
webpack "5"
"@storybook/preview-api@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.4.1.tgz#473db167a0c1b00a85a73c8ee04163cd3a6d7e2b"
integrity sha512-VdnESYfXCUasNtMd5s1Q8DPqMnAUdpROn8mE8UAD79Cy7DSNesI1q0SATuJqh5iYCT/+3Tpjfghsr2zC/mOh8w==
"@storybook/preview-api@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.4.2.tgz#77640e16c8662b9aa3a9dd4ec1b7362b9b4f6b3f"
integrity sha512-5X/xvIvDPaWJKUBCo5zVeBbbjkhnwcI2KPkuOgrHVRRhuQ5WqD0RYxVtOOFNyQXme7g0nNl5RFNgvT7qv9qGeg==
"@storybook/preview-api@^8.0.0":
version "8.3.5"
@@ -3153,22 +3144,22 @@
react-docgen-typescript "^2.2.2"
tslib "^2.0.0"
"@storybook/react-dom-shim@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.4.1.tgz#aa6d4bddd5517748865b6575a70435ed35669ff5"
integrity sha512-XhvuqkpqtcUjDA8XE4osq140SCddX3VHMdj+IwlrMdoSl32CAya01TH5YDDx6YMy6hM/QQbyVKaemG7RB/oU4Q==
"@storybook/react-dom-shim@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.4.2.tgz#cefc4b2cb7d3f632492867a3d5edbf568418c66a"
integrity sha512-FZVTM1f34FpGnf6e3MDIKkz05gmn8H9wEccvQAgr8pEFe8VWfrpVWeUrmatSAfgrCMNXYC1avDend8UX6IM8Fg==
"@storybook/react@8.4.1", "@storybook/react@^8.3.5":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.4.1.tgz#f75130b2bbed157f91de5f66b381ee063227c1a6"
integrity sha512-ZwszrzV47nWQEZ0X4LyNgv5OFq4iy/7LpmxW6IncIO7PWm70OWG2BVtKFNsNQx0LY+hOtllWZbvg06mPQzahFA==
"@storybook/react@8.4.2", "@storybook/react@^8.3.5":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.4.2.tgz#a8d61902e0b72e99e96dfde4251eb0ce79249905"
integrity sha512-rO5/aVKBVhIKENcL7G8ud4QKC5OyWBPCkJIvY6XUHIuhErJy9/4pP+sZ85jypVwx5kq+EqCPF8AEOWjIxB/4/Q==
dependencies:
"@storybook/components" "8.4.1"
"@storybook/components" "8.4.2"
"@storybook/global" "^5.0.0"
"@storybook/manager-api" "8.4.1"
"@storybook/preview-api" "8.4.1"
"@storybook/react-dom-shim" "8.4.1"
"@storybook/theming" "8.4.1"
"@storybook/manager-api" "8.4.2"
"@storybook/preview-api" "8.4.2"
"@storybook/react-dom-shim" "8.4.2"
"@storybook/theming" "8.4.2"
"@storybook/test-runner@^0.19.1":
version "0.19.1"
@@ -3198,24 +3189,24 @@
nyc "^15.1.0"
playwright "^1.14.0"
"@storybook/test@8.4.1", "@storybook/test@^8.3.5":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.4.1.tgz#e78d5f644466d2c8fa5b099f5ddb8a49204ed0dc"
integrity sha512-najn9kCxB8NaHykhD7Fv+Iq0FnxmIJYOJlYiI8NMgVLwaSDFf6gnqAY6HHVPRqkhej8TuT1L2e2RxKqzWEB+mA==
"@storybook/test@8.4.2", "@storybook/test@^8.3.5":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.4.2.tgz#aab7b4c6848b961f7550fa98157fca3bf2ac817c"
integrity sha512-MipTdboStv0hsqF2Sw8TZgP0YnxCcDYwxkTOd4hmRzev/7Brtvpi4pqjqh8k98ZCvhrCPAPVIoX5drk+oi3YUA==
dependencies:
"@storybook/csf" "^0.1.11"
"@storybook/global" "^5.0.0"
"@storybook/instrumenter" "8.4.1"
"@storybook/instrumenter" "8.4.2"
"@testing-library/dom" "10.4.0"
"@testing-library/jest-dom" "6.5.0"
"@testing-library/user-event" "14.5.2"
"@vitest/expect" "2.0.5"
"@vitest/spy" "2.0.5"
"@storybook/theming@8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.4.1.tgz#483497e4853497555c233b7a0b4a92181f7aeb98"
integrity sha512-Sz24isryVFZaVahXkjgnCsMAQqQeeKg41AtLsldlYdesIo6fr5tc6/SkTUy+CYadK4Dkhqp+vVRDnwToYYRGhA==
"@storybook/theming@8.4.2":
version "8.4.2"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.4.2.tgz#0e385869a225040e326cfba301b6cdccd31dcb21"
integrity sha512-9j4fnu5LcV+qSs1rdwf61Bt14lms0T1LOZkHxGNcS1c1oH+cPS+sxECh2lxtni+mvOAHUlBs9pKhVZzRPdWpvg==
"@supabase/auth-js@2.65.1":
version "2.65.1"
@@ -3255,12 +3246,13 @@
"@types/ws" "^8.5.10"
ws "^8.14.2"
"@supabase/ssr@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@supabase/ssr/-/ssr-0.5.1.tgz#8fa07d7cc3548ad9fbf73e9e84a51413fefae892"
integrity sha512-+G94H/GZG0nErZ3FQV9yJmsC5Rj7dmcfCAwOt37hxeR1La+QTl8cE9whzYwPUrTJjMLGNXoO+1BMvVxwBAbz4g==
"@supabase/ssr@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@supabase/ssr/-/ssr-0.5.2.tgz#04233a0ca23a5bc15e02006fb324b9ed2f9f645d"
integrity sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A==
dependencies:
cookie "^0.6.0"
"@types/cookie" "^0.6.0"
cookie "^0.7.0"
"@supabase/storage-js@2.7.1":
version "2.7.1"
@@ -3468,6 +3460,11 @@
dependencies:
"@types/node" "*"
"@types/cookie@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
"@types/d3-array@^3.0.3":
version "3.2.1"
resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz"
@@ -3642,10 +3639,10 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.8.7":
version "22.8.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.8.7.tgz#04ab7a073d95b4a6ee899f235d43f3c320a976f4"
integrity sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.9.0":
version "22.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.0.tgz#b7f16e5c3384788542c72dc3d561a7ceae2c0365"
integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==
dependencies:
undici-types "~6.19.8"
@@ -4030,19 +4027,19 @@
resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
"@xyflow/react@^12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.3.4.tgz#cccc57f7a992faecc5ed1dda82838b31c1afa522"
integrity sha512-KjFkj84S+wK8aJF/PORxSkOAeotTTPz++hus+Y95NFMIJGVyl8jjVaaz5B1zyV0prk6ZkbMp6q0vqMjJdZT25Q==
"@xyflow/react@^12.3.5":
version "12.3.5"
resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.3.5.tgz#88ca2efe2ddf1300bc171a2ef797f7cb41386ca4"
integrity sha512-wAYqpicdrVo1rxCu0X3M9s3YIF45Agqfabw0IBryTGqjWvr2NyfciI8gIP4MB+NKpWWN5kxZ9tiZ9u8lwC7iAg==
dependencies:
"@xyflow/system" "0.0.45"
"@xyflow/system" "0.0.46"
classcat "^5.0.3"
zustand "^4.4.0"
"@xyflow/system@0.0.45":
version "0.0.45"
resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.45.tgz#ca1f4d843d2925ce9c5763f16abf51a4c69953ef"
integrity sha512-szP1LjDD4jlRYYhxvgZqOCTMToUVNqjQkrlhb0fhv1sXomU1+yMDdhpQT+FjE4d+rKx08fS10sOuZUl2ycXaDw==
"@xyflow/system@0.0.46":
version "0.0.46"
resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.46.tgz#b0a5915d59c0ea5ca6d24e1eb90c5a0d7eda7864"
integrity sha512-bmFXvboVdiydIFZmDCjrbBCYgB0d5pYdkcZPWbAxGmhMRUZ+kW3CksYgYxWabrw51rwpWitLEadvLrivG0mVfA==
dependencies:
"@types/d3-drag" "^3.0.7"
"@types/d3-selection" "^3.0.10"
@@ -5091,15 +5088,15 @@ convert-source-map@^2.0.0:
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
cookie@0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.0.tgz#2148f68a77245d5c2c0005d264bc3e08cfa0655d"
integrity sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==
cookie@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.1.tgz#e1a00d20420e0266aff817815640289eef142751"
integrity sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==
cookie@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
cookie@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
core-js-compat@^3.38.0, core-js-compat@^3.38.1:
version "3.38.1"
@@ -6011,12 +6008,12 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-next@15.0.2:
version "15.0.2"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.0.2.tgz#b06ce97f88a4a0a82cbf3d353359ff5680d01f67"
integrity sha512-N8o6cyUXzlMmQbdc2Kc83g1qomFi3ITqrAZfubipVKET2uR2mCStyGRcx/r8WiAIVMul2KfwRiCHBkTpBvGBmA==
eslint-config-next@15.0.3:
version "15.0.3"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.0.3.tgz#b483585260d5e55050d4ab87e053c88089ae12ee"
integrity sha512-IGP2DdQQrgjcr4mwFPve4DrCqo7CVVez1WoYY47XwKSrYO4hC0Dlb+iJA60i0YfICOzgNADIb8r28BpQ5Zs0wg==
dependencies:
"@next/eslint-plugin-next" "15.0.2"
"@next/eslint-plugin-next" "15.0.3"
"@rushstack/eslint-patch" "^1.10.3"
"@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
"@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -8175,10 +8172,10 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lucide-react@^0.454.0:
version "0.454.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.454.0.tgz#a81b9c482018720f07ead0503ae502d94d528444"
integrity sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==
lucide-react@^0.456.0:
version "0.456.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.456.0.tgz#14906c3355cc65d3380b7b2294b331aeda1bb392"
integrity sha512-DIIGJqTT5X05sbAsQ+OhA8OtJYyD4NsEMCA/HQW/Y6ToPQ7gwbtujIoeAaup4HpHzV35SQOarKAWH8LYglB6eA==
lz-string@^1.5.0:
version "1.5.0"
@@ -9176,7 +9173,7 @@ pg-types@^2.2.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0:
picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
@@ -9339,12 +9336,12 @@ postcss@8.4.31:
source-map-js "^1.0.2"
postcss@^8, postcss@^8.2.14, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.38:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
version "8.4.48"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.48.tgz#765f3f8abaa2a2b065cdddbc57ad4cb5a76e515f"
integrity sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==
dependencies:
nanoid "^3.3.7"
picocolors "^1.1.0"
picocolors "^1.1.1"
source-map-js "^1.2.1"
postgres-array@~2.0.0:
@@ -9537,10 +9534,10 @@ react-confetti@^6.1.0:
dependencies:
tween-functions "^1.2.0"
react-day-picker@^9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-9.2.1.tgz#051bc8d0d72a1ea4c414bf7990a742c56a49d01b"
integrity sha512-rCoK4oJi9NBXt1nNdQFSa7gBG+hWsqVCtoLFLxvMzkVxDp+rSqsuUQ0LccJyLigwp/hX8XnAokTsT03+5lbjyA==
react-day-picker@^9.3.0:
version "9.3.0"
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-9.3.0.tgz#00e979a495d98ce8886bb3625c51133ba5c2acc8"
integrity sha512-xXgZISTXlwQ1Igt4cBttXF+aK1Xvd00azcGVY74PNCAe8PxtULFVWGT1UfdavFiVScF04dyV8QcybKZAw570QQ==
dependencies:
"@date-fns/tz" "^1.1.2"
date-fns "^4.1.0"
@@ -9574,10 +9571,10 @@ react-docgen@^7.0.0:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-hook-form@^7.53.1:
version "7.53.1"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.1.tgz#3f2cd1ed2b3af99416a4ac674da2d526625add67"
integrity sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==
react-hook-form@^7.53.2:
version "7.53.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.2.tgz#6fa37ae27330af81089baadd7f322cc987b8e2ac"
integrity sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==
react-icons@^5.3.0:
version "5.3.0"
@@ -10342,12 +10339,12 @@ stop-iteration-iterator@^1.0.0:
dependencies:
internal-slot "^1.0.4"
storybook@^8.4.1:
version "8.4.1"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.4.1.tgz#97baa471677566d614a5677deab0cd243c0142dc"
integrity sha512-0tfFIFghjho9FtnFoiJMoxhcs2iIdvEF81GTSVnTsDVJrYA84nB+FxN3UY1fT0BcQ8BFlbf+OhSjZL7ufqqWKA==
storybook@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.4.2.tgz#02e71cf32db25af713b3681b1b52be1403b478dd"
integrity sha512-GMCgyAulmLNrkUtDkCpFO4SB77YrpiIxq6e5tzaQdXEuaDu1mdNwOuP3VG7nE2FzxmqDvagSgriM68YW9iFaZA==
dependencies:
"@storybook/core" "8.4.1"
"@storybook/core" "8.4.2"
stream-browserify@^3.0.0:
version "3.0.0"
@@ -11116,10 +11113,10 @@ utila@~0.4:
resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz"
integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==
uuid@^11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.2.tgz#a8d68ba7347d051e7ea716cc8dcbbab634d66875"
integrity sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==
uuid@^11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d"
integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==
uuid@^8.3.2:
version "8.3.2"

View File

@@ -829,13 +829,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "pyright"
version = "1.1.387"
version = "1.1.388"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
files = [
{file = "pyright-1.1.387-py3-none-any.whl", hash = "sha256:6a1f495a261a72e12ad17e20d1ae3df4511223c773b19407cfa006229b1b08a5"},
{file = "pyright-1.1.387.tar.gz", hash = "sha256:577de60224f7fe36505d5b181231e3a395d427b7873be0bbcaa962a29ea93a60"},
{file = "pyright-1.1.388-py3-none-any.whl", hash = "sha256:c7068e9f2c23539c6ac35fc9efac6c6c1b9aa5a0ce97a9a8a6cf0090d7cbf84c"},
{file = "pyright-1.1.388.tar.gz", hash = "sha256:0166d19b716b77fd2d9055de29f71d844874dbc6b9d3472ccd22df91db3dfa34"},
]
[package.dependencies]
@@ -1058,29 +1058,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
version = "0.7.2"
version = "0.7.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
{file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
{file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
{file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
{file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
{file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
{file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
{file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
{file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
{file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
{file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
{file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
{file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
{file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
{file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
{file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
{file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
{file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
]
[[package]]
@@ -1298,4 +1298,4 @@ watchmedo = ["PyYAML (>=3.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "361b9204c1acb3460c52f93879693fc2a0b506b4806a0ec884f2b9307b9c2e2d"
content-hash = "cc73969e4aca097fb0832e02387d823095f06c7cd8516e56e8bc9026671ac9aa"

View File

@@ -28,8 +28,8 @@ pytest-asyncio = "^0.24.0"
pytest-watcher = "^0.4.3"
requests = "^2.32.3"
ruff = "^0.7.2"
pyright = "^1.1.387"
ruff = "^0.7.3"
pyright = "^1.1.388"
isort = "^5.13.2"
black = "^24.10.0"

View File

@@ -3,3 +3,5 @@ mkdocs-material
mkdocs-table-reader-plugin
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