mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Merge branch 'dev' into swiftyos/open-1920-marketplace-home-components
This commit is contained in:
@@ -9,6 +9,15 @@ repos:
|
||||
- id: check-merge-conflict
|
||||
- id: check-symlinks
|
||||
- id: debug-statements
|
||||
|
||||
- repo: https://github.com/Yelp/detect-secrets
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: detect-secrets
|
||||
name: Detect secrets
|
||||
description: Detects high entropy strings that are likely to be passwords.
|
||||
files: ^autogpt_platform/
|
||||
stages: [push]
|
||||
|
||||
- repo: local
|
||||
# isort needs the context of which packages are installed to function, so we
|
||||
@@ -42,7 +51,7 @@ repos:
|
||||
hooks:
|
||||
- id: black
|
||||
name: Lint (Black)
|
||||
language_version: python3.10
|
||||
language_version: python3.12
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
|
||||
@@ -46,9 +46,9 @@ To run the AutoGPT Platform, follow these steps:
|
||||
|
||||
6. Run the following command:
|
||||
```
|
||||
cp .env.example .env
|
||||
cp .env.example .env.local
|
||||
```
|
||||
This command will copy the `.env.example` file to `.env` in the `frontend` directory. You can modify the `.env` within this folder to add your own environment variables for the frontend application.
|
||||
This command will copy the `.env.example` file to `.env.local` in the `frontend` directory. You can modify the `.env.local` within this folder to add your own environment variables for the frontend application.
|
||||
|
||||
7. Run the following command:
|
||||
```
|
||||
|
||||
@@ -2,6 +2,8 @@ import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from redis import Redis
|
||||
from backend.executor.database import DatabaseManager
|
||||
@@ -10,13 +12,77 @@ from autogpt_libs.utils.cache import thread_cached
|
||||
from autogpt_libs.utils.synchronize import RedisKeyedMutex
|
||||
|
||||
from .types import (
|
||||
APIKeyCredentials,
|
||||
Credentials,
|
||||
OAuth2Credentials,
|
||||
OAuthState,
|
||||
UserMetadata,
|
||||
UserMetadataRaw,
|
||||
UserIntegrations,
|
||||
)
|
||||
|
||||
from backend.util.settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
revid_credentials = APIKeyCredentials(
|
||||
id="fdb7f412-f519-48d1-9b5f-d2f73d0e01fe",
|
||||
provider="revid",
|
||||
api_key=SecretStr(settings.secrets.revid_api_key),
|
||||
title="Use Credits for Revid",
|
||||
expires_at=None,
|
||||
)
|
||||
ideogram_credentials = APIKeyCredentials(
|
||||
id="760f84fc-b270-42de-91f6-08efe1b512d0",
|
||||
provider="ideogram",
|
||||
api_key=SecretStr(settings.secrets.ideogram_api_key),
|
||||
title="Use Credits for Ideogram",
|
||||
expires_at=None,
|
||||
)
|
||||
replicate_credentials = APIKeyCredentials(
|
||||
id="6b9fc200-4726-4973-86c9-cd526f5ce5db",
|
||||
provider="replicate",
|
||||
api_key=SecretStr(settings.secrets.replicate_api_key),
|
||||
title="Use Credits for Replicate",
|
||||
expires_at=None,
|
||||
)
|
||||
openai_credentials = APIKeyCredentials(
|
||||
id="53c25cb8-e3ee-465c-a4d1-e75a4c899c2a",
|
||||
provider="llm",
|
||||
api_key=SecretStr(settings.secrets.openai_api_key),
|
||||
title="Use Credits for OpenAI",
|
||||
expires_at=None,
|
||||
)
|
||||
anthropic_credentials = APIKeyCredentials(
|
||||
id="24e5d942-d9e3-4798-8151-90143ee55629",
|
||||
provider="llm",
|
||||
api_key=SecretStr(settings.secrets.anthropic_api_key),
|
||||
title="Use Credits for Anthropic",
|
||||
expires_at=None,
|
||||
)
|
||||
groq_credentials = APIKeyCredentials(
|
||||
id="4ec22295-8f97-4dd1-b42b-2c6957a02545",
|
||||
provider="llm",
|
||||
api_key=SecretStr(settings.secrets.groq_api_key),
|
||||
title="Use Credits for Groq",
|
||||
expires_at=None,
|
||||
)
|
||||
did_credentials = APIKeyCredentials(
|
||||
id="7f7b0654-c36b-4565-8fa7-9a52575dfae2",
|
||||
provider="d_id",
|
||||
api_key=SecretStr(settings.secrets.did_api_key),
|
||||
title="Use Credits for D-ID",
|
||||
expires_at=None,
|
||||
)
|
||||
|
||||
DEFAULT_CREDENTIALS = [
|
||||
revid_credentials,
|
||||
ideogram_credentials,
|
||||
replicate_credentials,
|
||||
openai_credentials,
|
||||
anthropic_credentials,
|
||||
groq_credentials,
|
||||
did_credentials,
|
||||
]
|
||||
|
||||
|
||||
class SupabaseIntegrationCredentialsStore:
|
||||
def __init__(self, redis: "Redis"):
|
||||
@@ -27,10 +93,11 @@ class SupabaseIntegrationCredentialsStore:
|
||||
def db_manager(self) -> "DatabaseManager":
|
||||
from backend.executor.database import DatabaseManager
|
||||
from backend.util.service import get_service_client
|
||||
|
||||
return get_service_client(DatabaseManager)
|
||||
|
||||
def add_creds(self, user_id: str, credentials: Credentials) -> None:
|
||||
with self.locked_user_metadata(user_id):
|
||||
with self.locked_user_integrations(user_id):
|
||||
if self.get_creds_by_id(user_id, credentials.id):
|
||||
raise ValueError(
|
||||
f"Can not re-create existing credentials #{credentials.id} "
|
||||
@@ -41,10 +108,23 @@ class SupabaseIntegrationCredentialsStore:
|
||||
)
|
||||
|
||||
def get_all_creds(self, user_id: str) -> list[Credentials]:
|
||||
user_metadata = self._get_user_metadata(user_id)
|
||||
return UserMetadata.model_validate(
|
||||
user_metadata.model_dump()
|
||||
).integration_credentials
|
||||
users_credentials = self._get_user_integrations(user_id).credentials
|
||||
all_credentials = users_credentials
|
||||
if settings.secrets.revid_api_key:
|
||||
all_credentials.append(revid_credentials)
|
||||
if settings.secrets.ideogram_api_key:
|
||||
all_credentials.append(ideogram_credentials)
|
||||
if settings.secrets.groq_api_key:
|
||||
all_credentials.append(groq_credentials)
|
||||
if settings.secrets.replicate_api_key:
|
||||
all_credentials.append(replicate_credentials)
|
||||
if settings.secrets.openai_api_key:
|
||||
all_credentials.append(openai_credentials)
|
||||
if settings.secrets.anthropic_api_key:
|
||||
all_credentials.append(anthropic_credentials)
|
||||
if settings.secrets.did_api_key:
|
||||
all_credentials.append(did_credentials)
|
||||
return all_credentials
|
||||
|
||||
def get_creds_by_id(self, user_id: str, credentials_id: str) -> Credentials | None:
|
||||
all_credentials = self.get_all_creds(user_id)
|
||||
@@ -59,7 +139,7 @@ class SupabaseIntegrationCredentialsStore:
|
||||
return list(set(c.provider for c in credentials))
|
||||
|
||||
def update_creds(self, user_id: str, updated: Credentials) -> None:
|
||||
with self.locked_user_metadata(user_id):
|
||||
with self.locked_user_integrations(user_id):
|
||||
current = self.get_creds_by_id(user_id, updated.id)
|
||||
if not current:
|
||||
raise ValueError(
|
||||
@@ -93,7 +173,7 @@ class SupabaseIntegrationCredentialsStore:
|
||||
self._set_user_integration_creds(user_id, updated_credentials_list)
|
||||
|
||||
def delete_creds_by_id(self, user_id: str, credentials_id: str) -> None:
|
||||
with self.locked_user_metadata(user_id):
|
||||
with self.locked_user_integrations(user_id):
|
||||
filtered_credentials = [
|
||||
c for c in self.get_all_creds(user_id) if c.id != credentials_id
|
||||
]
|
||||
@@ -110,14 +190,14 @@ class SupabaseIntegrationCredentialsStore:
|
||||
scopes=scopes,
|
||||
)
|
||||
|
||||
with self.locked_user_metadata(user_id):
|
||||
user_metadata = self._get_user_metadata(user_id)
|
||||
oauth_states = user_metadata.integration_oauth_states
|
||||
oauth_states.append(state.model_dump())
|
||||
user_metadata.integration_oauth_states = oauth_states
|
||||
with self.locked_user_integrations(user_id):
|
||||
user_integrations = self._get_user_integrations(user_id)
|
||||
oauth_states = user_integrations.oauth_states
|
||||
oauth_states.append(state)
|
||||
user_integrations.oauth_states = oauth_states
|
||||
|
||||
self.db_manager.update_user_metadata(
|
||||
user_id=user_id, metadata=user_metadata
|
||||
self.db_manager.update_user_integrations(
|
||||
user_id=user_id, data=user_integrations
|
||||
)
|
||||
|
||||
return token
|
||||
@@ -132,39 +212,39 @@ class SupabaseIntegrationCredentialsStore:
|
||||
IS TO CHECK IF THE USER HAS GIVEN PERMISSIONS TO THE APPLICATION BEFORE EXCHANGING
|
||||
THE CODE FOR TOKENS.
|
||||
"""
|
||||
user_metadata = self._get_user_metadata(user_id)
|
||||
oauth_states = user_metadata.integration_oauth_states
|
||||
user_integrations = self._get_user_integrations(user_id)
|
||||
oauth_states = user_integrations.oauth_states
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
valid_state = next(
|
||||
(
|
||||
state
|
||||
for state in oauth_states
|
||||
if state["token"] == token
|
||||
and state["provider"] == provider
|
||||
and state["expires_at"] > now.timestamp()
|
||||
if state.token == token
|
||||
and state.provider == provider
|
||||
and state.expires_at > now.timestamp()
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if valid_state:
|
||||
return valid_state.get("scopes", [])
|
||||
return valid_state.scopes
|
||||
|
||||
return []
|
||||
|
||||
def verify_state_token(self, user_id: str, token: str, provider: str) -> bool:
|
||||
with self.locked_user_metadata(user_id):
|
||||
user_metadata = self._get_user_metadata(user_id)
|
||||
oauth_states = user_metadata.integration_oauth_states
|
||||
with self.locked_user_integrations(user_id):
|
||||
user_integrations = self._get_user_integrations(user_id)
|
||||
oauth_states = user_integrations.oauth_states
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
valid_state = next(
|
||||
(
|
||||
state
|
||||
for state in oauth_states
|
||||
if state["token"] == token
|
||||
and state["provider"] == provider
|
||||
and state["expires_at"] > now.timestamp()
|
||||
if state.token == token
|
||||
and state.provider == provider
|
||||
and state.expires_at > now.timestamp()
|
||||
),
|
||||
None,
|
||||
)
|
||||
@@ -172,8 +252,8 @@ class SupabaseIntegrationCredentialsStore:
|
||||
if valid_state:
|
||||
# Remove the used state
|
||||
oauth_states.remove(valid_state)
|
||||
user_metadata.integration_oauth_states = oauth_states
|
||||
self.db_manager.update_user_metadata(user_id, user_metadata)
|
||||
user_integrations.oauth_states = oauth_states
|
||||
self.db_manager.update_user_integrations(user_id, user_integrations)
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -181,14 +261,18 @@ class SupabaseIntegrationCredentialsStore:
|
||||
def _set_user_integration_creds(
|
||||
self, user_id: str, credentials: list[Credentials]
|
||||
) -> None:
|
||||
raw_metadata = self._get_user_metadata(user_id)
|
||||
raw_metadata.integration_credentials = [c.model_dump() for c in credentials]
|
||||
self.db_manager.update_user_metadata(user_id, raw_metadata)
|
||||
integrations = self._get_user_integrations(user_id)
|
||||
# Remove default credentials from the list
|
||||
credentials = [c for c in credentials if c not in DEFAULT_CREDENTIALS]
|
||||
integrations.credentials = credentials
|
||||
self.db_manager.update_user_integrations(user_id, integrations)
|
||||
|
||||
def _get_user_metadata(self, user_id: str) -> UserMetadataRaw:
|
||||
metadata: UserMetadataRaw = self.db_manager.get_user_metadata(user_id=user_id)
|
||||
return metadata
|
||||
def _get_user_integrations(self, user_id: str) -> UserIntegrations:
|
||||
integrations: UserIntegrations = self.db_manager.get_user_integrations(
|
||||
user_id=user_id
|
||||
)
|
||||
return integrations
|
||||
|
||||
def locked_user_metadata(self, user_id: str):
|
||||
key = (self.db_manager, f"user:{user_id}", "metadata")
|
||||
def locked_user_integrations(self, user_id: str):
|
||||
key = (self.db_manager, f"user:{user_id}", "integrations")
|
||||
return self.locks.locked(key)
|
||||
|
||||
@@ -65,6 +65,11 @@ class UserMetadata(BaseModel):
|
||||
integration_oauth_states: list[OAuthState] = Field(default_factory=list)
|
||||
|
||||
|
||||
class UserMetadataRaw(BaseModel):
|
||||
integration_credentials: list[dict] = Field(default_factory=list)
|
||||
integration_oauth_states: list[dict] = Field(default_factory=list)
|
||||
class UserMetadataRaw(TypedDict, total=False):
|
||||
integration_credentials: list[dict]
|
||||
integration_oauth_states: list[dict]
|
||||
|
||||
|
||||
class UserIntegrations(BaseModel):
|
||||
credentials: list[Credentials] = Field(default_factory=list)
|
||||
oauth_states: list[OAuthState] = Field(default_factory=list)
|
||||
|
||||
@@ -7,6 +7,9 @@ PRISMA_SCHEMA="postgres/schema.prisma"
|
||||
|
||||
BACKEND_CORS_ALLOW_ORIGINS=["http://localhost:3000"]
|
||||
|
||||
# generate using `from cryptography.fernet import Fernet;Fernet.generate_key().decode()`
|
||||
ENCRYPTION_KEY='dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw='
|
||||
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=password
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
import logging
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="revid",
|
||||
api_key=SecretStr("mock-revid-api-key"),
|
||||
title="Mock Revid API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class AudioTrack(str, Enum):
|
||||
@@ -119,10 +136,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class AIShortformVideoCreatorBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="revid_api_key",
|
||||
description="Your revid.ai API key",
|
||||
placeholder="Enter your revid.ai API key",
|
||||
credentials: CredentialsMetaInput[Literal["revid"], Literal["api_key"]] = (
|
||||
CredentialsField(
|
||||
provider="revid",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The revid.ai integration can be used with "
|
||||
"any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
)
|
||||
script: str = SchemaField(
|
||||
description="""1. Use short and punctuated sentences\n\n2. Use linebreaks to create a new clip\n\n3. Text outside of brackets is spoken by the AI, and [text between brackets] will be used to guide the visual generation. For example, [close-up of a cat] will show a close-up of a cat.""",
|
||||
@@ -168,7 +188,7 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
input_schema=AIShortformVideoCreatorBlock.Input,
|
||||
output_schema=AIShortformVideoCreatorBlock.Output,
|
||||
test_input={
|
||||
"api_key": "test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"script": "[close-up of a cat] Meow!",
|
||||
"ratio": "9 / 16",
|
||||
"resolution": "720p",
|
||||
@@ -190,6 +210,7 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
"create_video": lambda api_key, payload: {"pid": "test_pid"},
|
||||
"wait_for_video": lambda api_key, pid, webhook_token, max_wait_time=1000: "https://example.com/video.mp4",
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def create_webhook(self):
|
||||
@@ -200,9 +221,9 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
webhook_data = response.json()
|
||||
return webhook_data["uuid"], f"https://webhook.site/{webhook_data['uuid']}"
|
||||
|
||||
def create_video(self, api_key: str, payload: dict) -> dict:
|
||||
def create_video(self, api_key: SecretStr, payload: dict) -> dict:
|
||||
url = "https://www.revid.ai/api/public/v2/render"
|
||||
headers = {"key": api_key}
|
||||
headers = {"key": api_key.get_secret_value()}
|
||||
response = requests.post(url, json=payload, headers=headers)
|
||||
logger.debug(
|
||||
f"API Response Status Code: {response.status_code}, Content: {response.text}"
|
||||
@@ -210,15 +231,19 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def check_video_status(self, api_key: str, pid: str) -> dict:
|
||||
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}
|
||||
headers = {"key": api_key.get_secret_value()}
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def wait_for_video(
|
||||
self, api_key: str, pid: str, webhook_token: str, max_wait_time: int = 1000
|
||||
self,
|
||||
api_key: SecretStr,
|
||||
pid: str,
|
||||
webhook_token: str,
|
||||
max_wait_time: int = 1000,
|
||||
) -> str:
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < max_wait_time:
|
||||
@@ -240,7 +265,9 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
logger.error("Video creation timed out")
|
||||
raise TimeoutError("Video creation timed out")
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
# Create a new Webhook.site URL
|
||||
webhook_token, webhook_url = self.create_webhook()
|
||||
logger.debug(f"Webhook URL: {webhook_url}")
|
||||
@@ -279,7 +306,7 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
}
|
||||
|
||||
logger.debug("Creating video...")
|
||||
response = self.create_video(input_data.api_key.get_secret_value(), payload)
|
||||
response = self.create_video(credentials.api_key, payload)
|
||||
pid = response.get("pid")
|
||||
|
||||
if not pid:
|
||||
@@ -291,8 +318,6 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
logger.debug(
|
||||
f"Video created with project ID: {pid}. Waiting for completion..."
|
||||
)
|
||||
video_url = self.wait_for_video(
|
||||
input_data.api_key.get_secret_value(), pid, webhook_token
|
||||
)
|
||||
video_url = self.wait_for_video(credentials.api_key, pid, webhook_token)
|
||||
logger.debug(f"Video ready: {video_url}")
|
||||
yield "video_url", video_url
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
import asyncio
|
||||
from typing import Literal
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
DiscordCredentials = CredentialsMetaInput[Literal["discord"], Literal["api_key"]]
|
||||
|
||||
|
||||
def DiscordCredentialsField() -> DiscordCredentials:
|
||||
return CredentialsField(
|
||||
description="Discord bot token",
|
||||
provider="discord",
|
||||
supported_credential_types={"api_key"},
|
||||
)
|
||||
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="discord",
|
||||
api_key=SecretStr("test_api_key"),
|
||||
title="Mock Discord API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class ReadDiscordMessagesBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
discord_bot_token: BlockSecret = SecretField(
|
||||
key="discord_bot_token", description="Discord bot token"
|
||||
)
|
||||
continuous_read: bool = SchemaField(
|
||||
description="Whether to continuously read messages", default=True
|
||||
)
|
||||
credentials: DiscordCredentials = DiscordCredentialsField()
|
||||
|
||||
class Output(BlockSchema):
|
||||
message_content: str = SchemaField(
|
||||
@@ -34,7 +57,11 @@ class ReadDiscordMessagesBlock(Block):
|
||||
output_schema=ReadDiscordMessagesBlock.Output, # Assign output schema
|
||||
description="Reads messages from a Discord channel using a bot token.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
test_input={"discord_bot_token": "test_token", "continuous_read": False},
|
||||
test_input={
|
||||
"continuous_read": False,
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
(
|
||||
"message_content",
|
||||
@@ -48,7 +75,7 @@ class ReadDiscordMessagesBlock(Block):
|
||||
},
|
||||
)
|
||||
|
||||
async def run_bot(self, token: str):
|
||||
async def run_bot(self, token: SecretStr):
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
@@ -81,19 +108,20 @@ class ReadDiscordMessagesBlock(Block):
|
||||
|
||||
await client.close()
|
||||
|
||||
await client.start(token)
|
||||
await client.start(token.get_secret_value())
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
while True:
|
||||
for output_name, output_value in self.__run(input_data):
|
||||
for output_name, output_value in self.__run(input_data, credentials):
|
||||
yield output_name, output_value
|
||||
if not input_data.continuous_read:
|
||||
break
|
||||
break
|
||||
|
||||
def __run(self, input_data: Input) -> BlockOutput:
|
||||
def __run(self, input_data: Input, credentials: APIKeyCredentials) -> BlockOutput:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
future = self.run_bot(input_data.discord_bot_token.get_secret_value())
|
||||
future = self.run_bot(credentials.api_key)
|
||||
|
||||
# If it's a Future (mock), set the result
|
||||
if isinstance(future, asyncio.Future):
|
||||
@@ -132,9 +160,7 @@ class ReadDiscordMessagesBlock(Block):
|
||||
|
||||
class SendDiscordMessageBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
discord_bot_token: BlockSecret = SecretField(
|
||||
key="discord_bot_token", description="Discord bot token"
|
||||
)
|
||||
credentials: DiscordCredentials = DiscordCredentialsField()
|
||||
message_content: str = SchemaField(
|
||||
description="The content of the message received"
|
||||
)
|
||||
@@ -155,14 +181,15 @@ class SendDiscordMessageBlock(Block):
|
||||
description="Sends a message to a Discord channel using a bot token.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
test_input={
|
||||
"discord_bot_token": "YOUR_DISCORD_BOT_TOKEN",
|
||||
"channel_name": "general",
|
||||
"message_content": "Hello, Discord!",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_output=[("status", "Message sent")],
|
||||
test_mock={
|
||||
"send_message": lambda token, channel_name, message_content: asyncio.Future()
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
async def send_message(self, token: str, channel_name: str, message_content: str):
|
||||
@@ -192,11 +219,13 @@ class SendDiscordMessageBlock(Block):
|
||||
"""Splits a message into chunks not exceeding the Discord limit."""
|
||||
return [message[i : i + limit] for i in range(0, len(message), limit)]
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
future = self.send_message(
|
||||
input_data.discord_bot_token.get_secret_value(),
|
||||
credentials.api_key.get_secret_value(),
|
||||
input_data.channel_name,
|
||||
input_data.message_content,
|
||||
)
|
||||
|
||||
@@ -43,6 +43,7 @@ class SendEmailBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
disabled=True,
|
||||
id="4335878a-394e-4e67-adf2-919877ff49ae",
|
||||
description="This block sends an email using the provided SMTP credentials.",
|
||||
categories={BlockCategory.OUTPUT},
|
||||
|
||||
@@ -79,12 +79,32 @@ class GmailReadBlock(Block):
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
(
|
||||
"result",
|
||||
"email",
|
||||
{
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
"from_": "test@example.com",
|
||||
"to": "recipient@example.com",
|
||||
"date": "2024-01-01",
|
||||
"body": "This is a test email",
|
||||
"sizeEstimate": 100,
|
||||
"attachments": [],
|
||||
},
|
||||
),
|
||||
(
|
||||
"emails",
|
||||
[
|
||||
{
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
"from_": "test@example.com",
|
||||
"to": "recipient@example.com",
|
||||
"date": "2024-01-01",
|
||||
"body": "This is a test email",
|
||||
"sizeEstimate": 100,
|
||||
"attachments": [],
|
||||
}
|
||||
],
|
||||
),
|
||||
@@ -95,6 +115,12 @@ class GmailReadBlock(Block):
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
"from_": "test@example.com",
|
||||
"to": "recipient@example.com",
|
||||
"date": "2024-01-01",
|
||||
"body": "This is a test email",
|
||||
"sizeEstimate": 100,
|
||||
"attachments": [],
|
||||
}
|
||||
],
|
||||
"_send_email": lambda *args, **kwargs: {"id": "1", "status": "sent"},
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
from typing import Literal
|
||||
|
||||
import googlemaps
|
||||
from pydantic import BaseModel
|
||||
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
|
||||
from pydantic import BaseModel, SecretStr
|
||||
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="google_maps",
|
||||
api_key=SecretStr("mock-google-maps-api-key"),
|
||||
title="Mock Google Maps API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class Place(BaseModel):
|
||||
@@ -16,8 +33,11 @@ class Place(BaseModel):
|
||||
|
||||
class GoogleMapsSearchBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="google_maps_api_key",
|
||||
credentials: CredentialsMetaInput[
|
||||
Literal["google_maps"], Literal["api_key"]
|
||||
] = CredentialsField(
|
||||
provider="google_maps",
|
||||
supported_credential_types={"api_key"},
|
||||
description="Google Maps API Key",
|
||||
)
|
||||
query: str = SchemaField(
|
||||
@@ -49,7 +69,7 @@ class GoogleMapsSearchBlock(Block):
|
||||
input_schema=GoogleMapsSearchBlock.Input,
|
||||
output_schema=GoogleMapsSearchBlock.Output,
|
||||
test_input={
|
||||
"api_key": "your_test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"query": "restaurants in new york",
|
||||
"radius": 5000,
|
||||
"max_results": 5,
|
||||
@@ -79,11 +99,14 @@ class GoogleMapsSearchBlock(Block):
|
||||
}
|
||||
]
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
places = self.search_places(
|
||||
input_data.api_key.get_secret_value(),
|
||||
credentials.api_key,
|
||||
input_data.query,
|
||||
input_data.radius,
|
||||
input_data.max_results,
|
||||
@@ -91,8 +114,8 @@ class GoogleMapsSearchBlock(Block):
|
||||
for place in places:
|
||||
yield "place", place
|
||||
|
||||
def search_places(self, api_key, query, radius, max_results):
|
||||
client = googlemaps.Client(key=api_key)
|
||||
def search_places(self, api_key: SecretStr, query, radius, max_results):
|
||||
client = googlemaps.Client(key=api_key.get_secret_value())
|
||||
return self._search_places(client, query, radius, max_results)
|
||||
|
||||
def _search_places(self, client, query, radius, max_results):
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, Literal, Optional
|
||||
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="ideogram",
|
||||
api_key=SecretStr("mock-ideogram-api-key"),
|
||||
title="Mock Ideogram API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class IdeogramModelName(str, Enum):
|
||||
@@ -62,9 +78,13 @@ class UpscaleOption(str, Enum):
|
||||
|
||||
class IdeogramModelBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="ideogram_api_key",
|
||||
description="Ideogram API Key",
|
||||
|
||||
credentials: CredentialsMetaInput[Literal["ideogram"], Literal["api_key"]] = (
|
||||
CredentialsField(
|
||||
provider="ideogram",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Ideogram integration can be used with any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
)
|
||||
prompt: str = SchemaField(
|
||||
description="Text prompt for image generation",
|
||||
@@ -132,7 +152,6 @@ class IdeogramModelBlock(Block):
|
||||
input_schema=IdeogramModelBlock.Input,
|
||||
output_schema=IdeogramModelBlock.Output,
|
||||
test_input={
|
||||
"api_key": "test_api_key",
|
||||
"ideogram_model_name": IdeogramModelName.V2,
|
||||
"prompt": "A futuristic cityscape at sunset",
|
||||
"aspect_ratio": AspectRatio.ASPECT_1_1,
|
||||
@@ -142,6 +161,7 @@ class IdeogramModelBlock(Block):
|
||||
"style_type": StyleType.AUTO,
|
||||
"negative_prompt": None,
|
||||
"color_palette_name": ColorPalettePreset.NONE,
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_output=[
|
||||
(
|
||||
@@ -153,14 +173,17 @@ class IdeogramModelBlock(Block):
|
||||
"run_model": lambda api_key, model_name, prompt, seed, aspect_ratio, magic_prompt_option, style_type, negative_prompt, color_palette_name: "https://ideogram.ai/api/images/test-generated-image-url.png",
|
||||
"upscale_image": lambda api_key, image_url: "https://ideogram.ai/api/images/test-upscaled-image-url.png",
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
seed = input_data.seed
|
||||
|
||||
# Step 1: Generate the image
|
||||
result = self.run_model(
|
||||
api_key=input_data.api_key.get_secret_value(),
|
||||
api_key=credentials.api_key,
|
||||
model_name=input_data.ideogram_model_name.value,
|
||||
prompt=input_data.prompt,
|
||||
seed=seed,
|
||||
@@ -174,7 +197,7 @@ class IdeogramModelBlock(Block):
|
||||
# Step 2: Upscale the image if requested
|
||||
if input_data.upscale == UpscaleOption.AI_UPSCALE:
|
||||
result = self.upscale_image(
|
||||
api_key=input_data.api_key.get_secret_value(),
|
||||
api_key=credentials.api_key,
|
||||
image_url=result,
|
||||
)
|
||||
|
||||
@@ -182,7 +205,7 @@ class IdeogramModelBlock(Block):
|
||||
|
||||
def run_model(
|
||||
self,
|
||||
api_key: str,
|
||||
api_key: SecretStr,
|
||||
model_name: str,
|
||||
prompt: str,
|
||||
seed: Optional[int],
|
||||
@@ -193,7 +216,10 @@ class IdeogramModelBlock(Block):
|
||||
color_palette_name: str,
|
||||
):
|
||||
url = "https://api.ideogram.ai/generate"
|
||||
headers = {"Api-Key": api_key, "Content-Type": "application/json"}
|
||||
headers = {
|
||||
"Api-Key": api_key.get_secret_value(),
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
data: Dict[str, Any] = {
|
||||
"image_request": {
|
||||
@@ -221,10 +247,10 @@ class IdeogramModelBlock(Block):
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"Failed to fetch image: {str(e)}")
|
||||
|
||||
def upscale_image(self, api_key: str, image_url: str):
|
||||
def upscale_image(self, api_key: SecretStr, image_url: str):
|
||||
url = "https://api.ideogram.ai/upscale"
|
||||
headers = {
|
||||
"Api-Key": api_key,
|
||||
"Api-Key": api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
@@ -3,7 +3,10 @@ import logging
|
||||
from enum import Enum, EnumMeta
|
||||
from json import JSONDecodeError
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, List, NamedTuple
|
||||
from typing import TYPE_CHECKING, Any, List, Literal, NamedTuple
|
||||
|
||||
from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials
|
||||
from pydantic import SecretStr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from enum import _EnumMemberT
|
||||
@@ -14,20 +17,44 @@ import openai
|
||||
from groq import Groq
|
||||
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
from backend.util import json
|
||||
from backend.util.settings import BehaveAs, Settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LlmApiKeys = {
|
||||
"openai": BlockSecret("openai_api_key"),
|
||||
"anthropic": BlockSecret("anthropic_api_key"),
|
||||
"groq": BlockSecret("groq_api_key"),
|
||||
"ollama": BlockSecret(value=""),
|
||||
# LlmApiKeys = {
|
||||
# "openai": BlockSecret("openai_api_key"),
|
||||
# "anthropic": BlockSecret("anthropic_api_key"),
|
||||
# "groq": BlockSecret("groq_api_key"),
|
||||
# "ollama": BlockSecret(value=""),
|
||||
# }
|
||||
|
||||
AICredentials = CredentialsMetaInput[Literal["llm"], Literal["api_key"]]
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="ed55ac19-356e-4243-a6cb-bc599e9b716f",
|
||||
provider="llm",
|
||||
api_key=SecretStr("mock-openai-api-key"),
|
||||
title="Mock OpenAI API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.title,
|
||||
}
|
||||
|
||||
|
||||
def AICredentialsField() -> AICredentials:
|
||||
return CredentialsField(
|
||||
description="API key for the LLM provider.",
|
||||
provider="llm",
|
||||
supported_credential_types={"api_key"},
|
||||
)
|
||||
|
||||
|
||||
class ModelMetadata(NamedTuple):
|
||||
provider: str
|
||||
context_window: int
|
||||
@@ -149,7 +176,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
description="The language model to use for answering the prompt.",
|
||||
advanced=False,
|
||||
)
|
||||
api_key: BlockSecret = SecretField(value="")
|
||||
credentials: AICredentials = AICredentialsField()
|
||||
sys_prompt: str = SchemaField(
|
||||
title="System Prompt",
|
||||
default="",
|
||||
@@ -188,13 +215,14 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
output_schema=AIStructuredResponseGeneratorBlock.Output,
|
||||
test_input={
|
||||
"model": LlmModel.GPT4_TURBO,
|
||||
"api_key": "fake-api",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"expected_format": {
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
"prompt": "User prompt",
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=("response", {"key1": "key1Value", "key2": "key2Value"}),
|
||||
test_mock={
|
||||
"llm_call": lambda *args, **kwargs: (
|
||||
@@ -212,7 +240,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
|
||||
@staticmethod
|
||||
def llm_call(
|
||||
api_key: str,
|
||||
credentials: APIKeyCredentials,
|
||||
llm_model: LlmModel,
|
||||
prompt: list[dict],
|
||||
json_format: bool,
|
||||
@@ -234,7 +262,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
provider = llm_model.metadata.provider
|
||||
|
||||
if provider == "openai":
|
||||
openai.api_key = api_key
|
||||
oai_client = openai.OpenAI(api_key=credentials.api_key.get_secret_value())
|
||||
response_format = None
|
||||
|
||||
if llm_model in [LlmModel.O1_MINI, LlmModel.O1_PREVIEW]:
|
||||
@@ -247,7 +275,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
elif json_format:
|
||||
response_format = {"type": "json_object"}
|
||||
|
||||
response = openai.chat.completions.create(
|
||||
response = oai_client.chat.completions.create(
|
||||
model=llm_model.value,
|
||||
messages=prompt, # type: ignore
|
||||
response_format=response_format, # type: ignore
|
||||
@@ -274,7 +302,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
# If the role is the same as the last one, combine the content
|
||||
messages[-1]["content"] += "\n" + p["content"]
|
||||
|
||||
client = anthropic.Anthropic(api_key=api_key)
|
||||
client = anthropic.Anthropic(api_key=credentials.api_key.get_secret_value())
|
||||
try:
|
||||
resp = client.messages.create(
|
||||
model=llm_model.value,
|
||||
@@ -293,7 +321,7 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
logger.error(error_message)
|
||||
raise ValueError(error_message)
|
||||
elif provider == "groq":
|
||||
client = Groq(api_key=api_key)
|
||||
client = Groq(api_key=credentials.api_key.get_secret_value())
|
||||
response_format = {"type": "json_object"} if json_format else None
|
||||
response = client.chat.completions.create(
|
||||
model=llm_model.value,
|
||||
@@ -322,7 +350,9 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
else:
|
||||
raise ValueError(f"Unsupported LLM provider: {provider}")
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
logger.debug(f"Calling LLM with input data: {input_data}")
|
||||
prompt = [p.model_dump() for p in input_data.conversation_history]
|
||||
|
||||
@@ -371,15 +401,11 @@ class AIStructuredResponseGeneratorBlock(Block):
|
||||
logger.info(f"LLM request: {prompt}")
|
||||
retry_prompt = ""
|
||||
llm_model = input_data.model
|
||||
api_key = (
|
||||
input_data.api_key.get_secret_value()
|
||||
or LlmApiKeys[llm_model.metadata.provider].get_secret_value()
|
||||
)
|
||||
|
||||
for retry_count in range(input_data.retry):
|
||||
try:
|
||||
response_text, input_token, output_token = self.llm_call(
|
||||
api_key=api_key,
|
||||
credentials=credentials,
|
||||
llm_model=llm_model,
|
||||
prompt=prompt,
|
||||
json_format=bool(input_data.expected_format),
|
||||
@@ -451,7 +477,7 @@ class AITextGeneratorBlock(Block):
|
||||
description="The language model to use for answering the prompt.",
|
||||
advanced=False,
|
||||
)
|
||||
api_key: BlockSecret = SecretField(value="")
|
||||
credentials: AICredentials = AICredentialsField()
|
||||
sys_prompt: str = SchemaField(
|
||||
title="System Prompt",
|
||||
default="",
|
||||
@@ -484,23 +510,33 @@ class AITextGeneratorBlock(Block):
|
||||
categories={BlockCategory.AI},
|
||||
input_schema=AITextGeneratorBlock.Input,
|
||||
output_schema=AITextGeneratorBlock.Output,
|
||||
test_input={"prompt": "User prompt"},
|
||||
test_input={
|
||||
"prompt": "User prompt",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=("response", "Response text"),
|
||||
test_mock={"llm_call": lambda *args, **kwargs: "Response text"},
|
||||
)
|
||||
|
||||
def llm_call(self, input_data: AIStructuredResponseGeneratorBlock.Input) -> str:
|
||||
def llm_call(
|
||||
self,
|
||||
input_data: AIStructuredResponseGeneratorBlock.Input,
|
||||
credentials: APIKeyCredentials,
|
||||
) -> str:
|
||||
block = AIStructuredResponseGeneratorBlock()
|
||||
response = block.run_once(input_data, "response")
|
||||
response = block.run_once(input_data, "response", credentials=credentials)
|
||||
self.merge_stats(block.execution_stats)
|
||||
return response["response"]
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
object_input_data = AIStructuredResponseGeneratorBlock.Input(
|
||||
**{attr: getattr(input_data, attr) for attr in input_data.model_fields},
|
||||
expected_format={},
|
||||
)
|
||||
yield "response", self.llm_call(object_input_data)
|
||||
yield "response", self.llm_call(object_input_data, credentials)
|
||||
|
||||
|
||||
class SummaryStyle(Enum):
|
||||
@@ -531,7 +567,7 @@ class AITextSummarizerBlock(Block):
|
||||
default=SummaryStyle.CONCISE,
|
||||
description="The style of the summary to generate.",
|
||||
)
|
||||
api_key: BlockSecret = SecretField(value="")
|
||||
credentials: AICredentials = AICredentialsField()
|
||||
# TODO: Make this dynamic
|
||||
max_tokens: int = SchemaField(
|
||||
title="Max Tokens",
|
||||
@@ -557,10 +593,14 @@ class AITextSummarizerBlock(Block):
|
||||
categories={BlockCategory.AI, BlockCategory.TEXT},
|
||||
input_schema=AITextSummarizerBlock.Input,
|
||||
output_schema=AITextSummarizerBlock.Output,
|
||||
test_input={"text": "Lorem ipsum..." * 100},
|
||||
test_input={
|
||||
"text": "Lorem ipsum..." * 100,
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=("summary", "Final summary of a long text"),
|
||||
test_mock={
|
||||
"llm_call": lambda input_data: (
|
||||
"llm_call": lambda input_data, credentials: (
|
||||
{"final_summary": "Final summary of a long text"}
|
||||
if "final_summary" in input_data.expected_format
|
||||
else {"summary": "Summary of a chunk of text"}
|
||||
@@ -568,21 +608,23 @@ class AITextSummarizerBlock(Block):
|
||||
},
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
for output in self._run(input_data):
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
for output in self._run(input_data, credentials):
|
||||
yield output
|
||||
|
||||
def _run(self, input_data: Input) -> BlockOutput:
|
||||
def _run(self, input_data: Input, credentials: APIKeyCredentials) -> BlockOutput:
|
||||
chunks = self._split_text(
|
||||
input_data.text, input_data.max_tokens, input_data.chunk_overlap
|
||||
)
|
||||
summaries = []
|
||||
|
||||
for chunk in chunks:
|
||||
chunk_summary = self._summarize_chunk(chunk, input_data)
|
||||
chunk_summary = self._summarize_chunk(chunk, input_data, credentials)
|
||||
summaries.append(chunk_summary)
|
||||
|
||||
final_summary = self._combine_summaries(summaries, input_data)
|
||||
final_summary = self._combine_summaries(summaries, input_data, credentials)
|
||||
yield "summary", final_summary
|
||||
|
||||
@staticmethod
|
||||
@@ -597,27 +639,36 @@ class AITextSummarizerBlock(Block):
|
||||
|
||||
return chunks
|
||||
|
||||
def llm_call(self, input_data: AIStructuredResponseGeneratorBlock.Input) -> dict:
|
||||
def llm_call(
|
||||
self,
|
||||
input_data: AIStructuredResponseGeneratorBlock.Input,
|
||||
credentials: APIKeyCredentials,
|
||||
) -> dict:
|
||||
block = AIStructuredResponseGeneratorBlock()
|
||||
response = block.run_once(input_data, "response")
|
||||
response = block.run_once(input_data, "response", credentials=credentials)
|
||||
self.merge_stats(block.execution_stats)
|
||||
return response
|
||||
|
||||
def _summarize_chunk(self, chunk: str, input_data: Input) -> str:
|
||||
def _summarize_chunk(
|
||||
self, chunk: str, input_data: Input, credentials: APIKeyCredentials
|
||||
) -> str:
|
||||
prompt = f"Summarize the following text in a {input_data.style} form. Focus your summary on the topic of `{input_data.focus}` if present, otherwise just provide a general summary:\n\n```{chunk}```"
|
||||
|
||||
llm_response = self.llm_call(
|
||||
AIStructuredResponseGeneratorBlock.Input(
|
||||
prompt=prompt,
|
||||
api_key=input_data.api_key,
|
||||
credentials=input_data.credentials,
|
||||
model=input_data.model,
|
||||
expected_format={"summary": "The summary of the given text."},
|
||||
)
|
||||
),
|
||||
credentials=credentials,
|
||||
)
|
||||
|
||||
return llm_response["summary"]
|
||||
|
||||
def _combine_summaries(self, summaries: list[str], input_data: Input) -> str:
|
||||
def _combine_summaries(
|
||||
self, summaries: list[str], input_data: Input, credentials: APIKeyCredentials
|
||||
) -> str:
|
||||
combined_text = "\n\n".join(summaries)
|
||||
|
||||
if len(combined_text.split()) <= input_data.max_tokens:
|
||||
@@ -626,12 +677,13 @@ class AITextSummarizerBlock(Block):
|
||||
llm_response = self.llm_call(
|
||||
AIStructuredResponseGeneratorBlock.Input(
|
||||
prompt=prompt,
|
||||
api_key=input_data.api_key,
|
||||
credentials=input_data.credentials,
|
||||
model=input_data.model,
|
||||
expected_format={
|
||||
"final_summary": "The final summary of all provided summaries."
|
||||
},
|
||||
)
|
||||
),
|
||||
credentials=credentials,
|
||||
)
|
||||
|
||||
return llm_response["final_summary"]
|
||||
@@ -640,11 +692,12 @@ class AITextSummarizerBlock(Block):
|
||||
return self._run(
|
||||
AITextSummarizerBlock.Input(
|
||||
text=combined_text,
|
||||
api_key=input_data.api_key,
|
||||
credentials=input_data.credentials,
|
||||
model=input_data.model,
|
||||
max_tokens=input_data.max_tokens,
|
||||
chunk_overlap=input_data.chunk_overlap,
|
||||
)
|
||||
),
|
||||
credentials=credentials,
|
||||
).send(None)[
|
||||
1
|
||||
] # Get the first yielded value
|
||||
@@ -660,9 +713,7 @@ class AIConversationBlock(Block):
|
||||
default=LlmModel.GPT4_TURBO,
|
||||
description="The language model to use for the conversation.",
|
||||
)
|
||||
api_key: BlockSecret = SecretField(
|
||||
value="", description="API key for the chosen language model provider."
|
||||
)
|
||||
credentials: AICredentials = AICredentialsField()
|
||||
max_tokens: int | None = SchemaField(
|
||||
advanced=True,
|
||||
default=None,
|
||||
@@ -693,8 +744,9 @@ class AIConversationBlock(Block):
|
||||
{"role": "user", "content": "Where was it played?"},
|
||||
],
|
||||
"model": LlmModel.GPT4_TURBO,
|
||||
"api_key": "test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=(
|
||||
"response",
|
||||
"The 2020 World Series was played at Globe Life Field in Arlington, Texas.",
|
||||
@@ -704,22 +756,29 @@ class AIConversationBlock(Block):
|
||||
},
|
||||
)
|
||||
|
||||
def llm_call(self, input_data: AIStructuredResponseGeneratorBlock.Input) -> str:
|
||||
def llm_call(
|
||||
self,
|
||||
input_data: AIStructuredResponseGeneratorBlock.Input,
|
||||
credentials: APIKeyCredentials,
|
||||
) -> str:
|
||||
block = AIStructuredResponseGeneratorBlock()
|
||||
response = block.run_once(input_data, "response")
|
||||
response = block.run_once(input_data, "response", credentials=credentials)
|
||||
self.merge_stats(block.execution_stats)
|
||||
return response["response"]
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
response = self.llm_call(
|
||||
AIStructuredResponseGeneratorBlock.Input(
|
||||
prompt="",
|
||||
api_key=input_data.api_key,
|
||||
credentials=input_data.credentials,
|
||||
model=input_data.model,
|
||||
conversation_history=input_data.messages,
|
||||
max_tokens=input_data.max_tokens,
|
||||
expected_format={},
|
||||
)
|
||||
),
|
||||
credentials=credentials,
|
||||
)
|
||||
|
||||
yield "response", response
|
||||
@@ -745,7 +804,7 @@ class AIListGeneratorBlock(Block):
|
||||
description="The language model to use for generating the list.",
|
||||
advanced=True,
|
||||
)
|
||||
api_key: BlockSecret = SecretField(value="")
|
||||
credentials: AICredentials = AICredentialsField()
|
||||
max_retries: int = SchemaField(
|
||||
default=3,
|
||||
description="Maximum number of retries for generating a valid list.",
|
||||
@@ -785,9 +844,10 @@ class AIListGeneratorBlock(Block):
|
||||
"fictional worlds."
|
||||
),
|
||||
"model": LlmModel.GPT4_TURBO,
|
||||
"api_key": "test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"max_retries": 3,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
(
|
||||
"generated_list",
|
||||
@@ -800,7 +860,7 @@ class AIListGeneratorBlock(Block):
|
||||
("list_item", "Draknos"),
|
||||
],
|
||||
test_mock={
|
||||
"llm_call": lambda input_data: {
|
||||
"llm_call": lambda input_data, credentials: {
|
||||
"response": "['Zylora Prime', 'Kharon-9', 'Vortexia', 'Oceara', 'Draknos']"
|
||||
},
|
||||
},
|
||||
@@ -809,9 +869,10 @@ class AIListGeneratorBlock(Block):
|
||||
@staticmethod
|
||||
def llm_call(
|
||||
input_data: AIStructuredResponseGeneratorBlock.Input,
|
||||
credentials: APIKeyCredentials,
|
||||
) -> dict[str, str]:
|
||||
llm_block = AIStructuredResponseGeneratorBlock()
|
||||
response = llm_block.run_once(input_data, "response")
|
||||
response = llm_block.run_once(input_data, "response", credentials=credentials)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
@@ -833,14 +894,13 @@ class AIListGeneratorBlock(Block):
|
||||
logger.error(f"Failed to convert string to list: {e}")
|
||||
raise ValueError("Invalid list format. Could not convert to list.")
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
logger.debug(f"Starting AIListGeneratorBlock.run with input data: {input_data}")
|
||||
|
||||
# Check for API key
|
||||
api_key_check = (
|
||||
input_data.api_key.get_secret_value()
|
||||
or LlmApiKeys[input_data.model.metadata.provider].get_secret_value()
|
||||
)
|
||||
api_key_check = credentials.api_key.get_secret_value()
|
||||
if not api_key_check:
|
||||
raise ValueError("No LLM API key provided.")
|
||||
|
||||
@@ -904,10 +964,11 @@ class AIListGeneratorBlock(Block):
|
||||
AIStructuredResponseGeneratorBlock.Input(
|
||||
sys_prompt=sys_prompt,
|
||||
prompt=prompt,
|
||||
api_key=input_data.api_key,
|
||||
credentials=input_data.credentials,
|
||||
model=input_data.model,
|
||||
expected_format={}, # Do not use structured response
|
||||
)
|
||||
),
|
||||
credentials=credentials,
|
||||
)
|
||||
|
||||
logger.debug(f"LLM response: {llm_response}")
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
from typing import List, 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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import (
|
||||
BlockSecret,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
SchemaField,
|
||||
SecretField,
|
||||
)
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="medium",
|
||||
api_key=SecretStr("mock-medium-api-key"),
|
||||
title="Mock Medium API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class PublishToMediumStatus(str, Enum):
|
||||
@@ -55,10 +77,12 @@ class PublishToMediumBlock(Block):
|
||||
description="Whether to notify followers that the user has published",
|
||||
placeholder="False",
|
||||
)
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="medium_api_key",
|
||||
description="""The API key for the Medium integration. You can get this from https://medium.com/me/settings/security and scrolling down to "integration Tokens".""",
|
||||
placeholder="Enter your Medium API key",
|
||||
credentials: CredentialsMetaInput[Literal["medium"], Literal["api_key"]] = (
|
||||
CredentialsField(
|
||||
provider="medium",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Medium integration can be used with any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
@@ -87,7 +111,7 @@ class PublishToMediumBlock(Block):
|
||||
"license": "all-rights-reserved",
|
||||
"notify_followers": False,
|
||||
"publish_status": PublishToMediumStatus.DRAFT.value,
|
||||
"api_key": "your_test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_output=[
|
||||
("post_id", "e6f36a"),
|
||||
@@ -104,11 +128,12 @@ class PublishToMediumBlock(Block):
|
||||
}
|
||||
}
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def create_post(
|
||||
self,
|
||||
api_key,
|
||||
api_key: SecretStr,
|
||||
author_id,
|
||||
title,
|
||||
content,
|
||||
@@ -120,7 +145,7 @@ class PublishToMediumBlock(Block):
|
||||
notify_followers,
|
||||
):
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Authorization": f"Bearer {api_key.get_secret_value()}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
@@ -144,9 +169,11 @@ class PublishToMediumBlock(Block):
|
||||
|
||||
return response.json()
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
response = self.create_post(
|
||||
input_data.api_key.get_secret_value(),
|
||||
credentials.api_key,
|
||||
input_data.author_id.get_secret_value(),
|
||||
input_data.title,
|
||||
input_data.content,
|
||||
|
||||
@@ -70,6 +70,7 @@ class GetRedditPostsBlock(Block):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
disabled=True,
|
||||
id="c6731acb-4285-4ee1-bc9b-03d0766c370f",
|
||||
description="This block fetches Reddit posts from a defined subreddit name.",
|
||||
categories={BlockCategory.SOCIAL},
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import os
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
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 name enum
|
||||
@@ -32,9 +49,13 @@ class ImageType(str, Enum):
|
||||
|
||||
class ReplicateFluxAdvancedModelBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="replicate_api_key",
|
||||
description="Replicate API Key",
|
||||
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="Text prompt for image generation",
|
||||
@@ -110,7 +131,7 @@ class ReplicateFluxAdvancedModelBlock(Block):
|
||||
input_schema=ReplicateFluxAdvancedModelBlock.Input,
|
||||
output_schema=ReplicateFluxAdvancedModelBlock.Output,
|
||||
test_input={
|
||||
"api_key": "test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"replicate_model_name": ReplicateFluxModelName.FLUX_SCHNELL,
|
||||
"prompt": "A beautiful landscape painting of a serene lake at sunrise",
|
||||
"seed": None,
|
||||
@@ -131,9 +152,12 @@ class ReplicateFluxAdvancedModelBlock(Block):
|
||||
test_mock={
|
||||
"run_model": lambda api_key, model_name, prompt, seed, steps, guidance, interval, aspect_ratio, output_format, output_quality, safety_tolerance: "https://replicate.com/output/generated-image-url.jpg",
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
# If the seed is not provided, generate a random seed
|
||||
seed = input_data.seed
|
||||
if seed is None:
|
||||
@@ -141,7 +165,7 @@ class ReplicateFluxAdvancedModelBlock(Block):
|
||||
|
||||
# Run the model using the provided inputs
|
||||
result = self.run_model(
|
||||
api_key=input_data.api_key.get_secret_value(),
|
||||
api_key=credentials.api_key,
|
||||
model_name=input_data.replicate_model_name.api_name,
|
||||
prompt=input_data.prompt,
|
||||
seed=seed,
|
||||
@@ -157,7 +181,7 @@ class ReplicateFluxAdvancedModelBlock(Block):
|
||||
|
||||
def run_model(
|
||||
self,
|
||||
api_key,
|
||||
api_key: SecretStr,
|
||||
model_name,
|
||||
prompt,
|
||||
seed,
|
||||
@@ -170,7 +194,7 @@ class ReplicateFluxAdvancedModelBlock(Block):
|
||||
safety_tolerance,
|
||||
):
|
||||
# Initialize Replicate client with the API key
|
||||
client = replicate.Client(api_token=api_key)
|
||||
client = replicate.Client(api_token=api_key.get_secret_value())
|
||||
|
||||
# Run the model with additional parameters
|
||||
output = client.run(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
from urllib.parse import quote
|
||||
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
|
||||
class GetRequest:
|
||||
@@ -120,12 +122,34 @@ class ExtractWebsiteContentBlock(Block, GetRequest):
|
||||
yield "content", content
|
||||
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="openweathermap",
|
||||
api_key=SecretStr("mock-openweathermap-api-key"),
|
||||
title="Mock OpenWeatherMap API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class GetWeatherInformationBlock(Block, GetRequest):
|
||||
class Input(BlockSchema):
|
||||
location: str = SchemaField(
|
||||
description="Location to get weather information for"
|
||||
)
|
||||
api_key: BlockSecret = SecretField(key="openweathermap_api_key")
|
||||
credentials: CredentialsMetaInput[
|
||||
Literal["openweathermap"], Literal["api_key"]
|
||||
] = CredentialsField(
|
||||
provider="openweathermap",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The OpenWeatherMap integration can be used with "
|
||||
"any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
use_celsius: bool = SchemaField(
|
||||
default=True,
|
||||
description="Whether to use Celsius or Fahrenheit for temperature",
|
||||
@@ -151,8 +175,8 @@ class GetWeatherInformationBlock(Block, GetRequest):
|
||||
description="Retrieves weather information for a specified location using OpenWeatherMap API.",
|
||||
test_input={
|
||||
"location": "New York",
|
||||
"api_key": "YOUR_API_KEY",
|
||||
"use_celsius": True,
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_output=[
|
||||
("temperature", "21.66"),
|
||||
@@ -165,11 +189,14 @@ class GetWeatherInformationBlock(Block, GetRequest):
|
||||
"weather": [{"description": "overcast clouds"}],
|
||||
}
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
units = "metric" if input_data.use_celsius else "imperial"
|
||||
api_key = input_data.api_key.get_secret_value()
|
||||
api_key = credentials.api_key
|
||||
location = input_data.location
|
||||
url = f"http://api.openweathermap.org/data/2.5/weather?q={quote(location)}&appid={api_key}&units={units}"
|
||||
weather_data = self.get_request(url, json=True)
|
||||
|
||||
@@ -2,15 +2,36 @@ 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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="d_id",
|
||||
api_key=SecretStr("mock-d-id-api-key"),
|
||||
title="Mock D-ID API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class CreateTalkingAvatarVideoBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="did_api_key", description="D-ID API Key"
|
||||
credentials: CredentialsMetaInput[Literal["d_id"], Literal["api_key"]] = (
|
||||
CredentialsField(
|
||||
provider="d_id",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The D-ID integration can be used with "
|
||||
"any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
)
|
||||
script_input: str = SchemaField(
|
||||
description="The text input for the script",
|
||||
@@ -58,7 +79,7 @@ class CreateTalkingAvatarVideoBlock(Block):
|
||||
input_schema=CreateTalkingAvatarVideoBlock.Input,
|
||||
output_schema=CreateTalkingAvatarVideoBlock.Output,
|
||||
test_input={
|
||||
"api_key": "your_test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
"script_input": "Welcome to AutoGPT",
|
||||
"voice_id": "en-US-JennyNeural",
|
||||
"presenter_id": "amy-Aq6OmGZnMt",
|
||||
@@ -86,27 +107,33 @@ class CreateTalkingAvatarVideoBlock(Block):
|
||||
"result_url": "https://d-id.com/api/clips/abcd1234-5678-efgh-ijkl-mnopqrstuvwx/video",
|
||||
},
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
def create_clip(self, api_key: str, payload: dict) -> dict:
|
||||
def create_clip(self, api_key: SecretStr, payload: dict) -> dict:
|
||||
url = "https://api.d-id.com/clips"
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"content-type": "application/json",
|
||||
"authorization": f"Basic {api_key}",
|
||||
"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: str, clip_id: str) -> dict:
|
||||
def get_clip_status(self, api_key: SecretStr, clip_id: str) -> dict:
|
||||
url = f"https://api.d-id.com/clips/{clip_id}"
|
||||
headers = {"accept": "application/json", "authorization": f"Basic {api_key}"}
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"authorization": f"Basic {api_key.get_secret_value()}",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
# Create the clip
|
||||
payload = {
|
||||
"script": {
|
||||
@@ -125,14 +152,12 @@ class CreateTalkingAvatarVideoBlock(Block):
|
||||
"driver_id": input_data.driver_id,
|
||||
}
|
||||
|
||||
response = self.create_clip(input_data.api_key.get_secret_value(), payload)
|
||||
response = self.create_clip(credentials.api_key, payload)
|
||||
clip_id = response["id"]
|
||||
|
||||
# Poll for clip status
|
||||
for _ in range(input_data.max_polling_attempts):
|
||||
status_response = self.get_clip_status(
|
||||
input_data.api_key.get_secret_value(), clip_id
|
||||
)
|
||||
status_response = self.get_clip_status(credentials.api_key, clip_id)
|
||||
if status_response["status"] == "done":
|
||||
yield "video_url", status_response["result_url"]
|
||||
return
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
from typing import Any
|
||||
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 BlockSecret, SchemaField, SecretField
|
||||
from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="unreal_speech",
|
||||
api_key=SecretStr("mock-unreal-speech-api-key"),
|
||||
title="Mock Unreal Speech API key",
|
||||
expires_at=None,
|
||||
)
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.type,
|
||||
}
|
||||
|
||||
|
||||
class UnrealTextToSpeechBlock(Block):
|
||||
@@ -17,8 +33,13 @@ class UnrealTextToSpeechBlock(Block):
|
||||
placeholder="Scarlett",
|
||||
default="Scarlett",
|
||||
)
|
||||
api_key: BlockSecret = SecretField(
|
||||
key="unreal_speech_api_key", description="Your Unreal Speech API key"
|
||||
credentials: CredentialsMetaInput[
|
||||
Literal["unreal_speech"], Literal["api_key"]
|
||||
] = CredentialsField(
|
||||
provider="unreal_speech",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Unreal Speech integration can be used with "
|
||||
"any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
@@ -35,7 +56,7 @@ class UnrealTextToSpeechBlock(Block):
|
||||
test_input={
|
||||
"text": "This is a test of the text to speech API.",
|
||||
"voice_id": "Scarlett",
|
||||
"api_key": "test_api_key",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_output=[("mp3_url", "https://example.com/test.mp3")],
|
||||
test_mock={
|
||||
@@ -43,15 +64,16 @@ class UnrealTextToSpeechBlock(Block):
|
||||
"OutputUri": "https://example.com/test.mp3"
|
||||
}
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def call_unreal_speech_api(
|
||||
api_key: str, text: str, voice_id: str
|
||||
api_key: SecretStr, text: str, voice_id: str
|
||||
) -> dict[str, Any]:
|
||||
url = "https://api.v7.unrealspeech.com/speech"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Authorization": f"Bearer {api_key.get_secret_value()}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
@@ -67,9 +89,11 @@ class UnrealTextToSpeechBlock(Block):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
api_response = self.call_unreal_speech_api(
|
||||
input_data.api_key.get_secret_value(),
|
||||
credentials.api_key,
|
||||
input_data.text,
|
||||
input_data.voice_id,
|
||||
)
|
||||
|
||||
@@ -4,11 +4,22 @@ from enum import Enum
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
import prisma.errors
|
||||
from autogpt_libs.supabase_integration_credentials_store.store import (
|
||||
anthropic_credentials,
|
||||
did_credentials,
|
||||
groq_credentials,
|
||||
ideogram_credentials,
|
||||
openai_credentials,
|
||||
replicate_credentials,
|
||||
revid_credentials,
|
||||
)
|
||||
from prisma import Json
|
||||
from prisma.enums import UserBlockCreditType
|
||||
from prisma.models import UserBlockCredit
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock
|
||||
from backend.blocks.ideogram import IdeogramModelBlock
|
||||
from backend.blocks.llm import (
|
||||
MODEL_METADATA,
|
||||
AIConversationBlock,
|
||||
@@ -17,6 +28,7 @@ from backend.blocks.llm import (
|
||||
AITextSummarizerBlock,
|
||||
LlmModel,
|
||||
)
|
||||
from backend.blocks.replicate_flux_advanced import ReplicateFluxAdvancedModelBlock
|
||||
from backend.blocks.search import ExtractWebsiteContentBlock, SearchTheWebBlock
|
||||
from backend.blocks.talking_head import CreateTalkingAvatarVideoBlock
|
||||
from backend.data.block import Block, BlockInput, get_block
|
||||
@@ -49,23 +61,70 @@ class BlockCost(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
llm_cost = [
|
||||
BlockCost(
|
||||
cost_type=BlockCostType.RUN,
|
||||
cost_filter={
|
||||
"model": model,
|
||||
"api_key": None, # Running LLM with user own API key is free.
|
||||
},
|
||||
cost_amount=metadata.cost_factor,
|
||||
)
|
||||
for model, metadata in MODEL_METADATA.items()
|
||||
] + [
|
||||
BlockCost(
|
||||
# Default cost is running LlmModel.GPT4O.
|
||||
cost_amount=MODEL_METADATA[LlmModel.GPT4O].cost_factor,
|
||||
cost_filter={"api_key": None},
|
||||
),
|
||||
]
|
||||
llm_cost = (
|
||||
[
|
||||
BlockCost(
|
||||
cost_type=BlockCostType.RUN,
|
||||
cost_filter={
|
||||
"model": model,
|
||||
"api_key": None, # Running LLM with user own API key is free.
|
||||
},
|
||||
cost_amount=metadata.cost_factor,
|
||||
)
|
||||
for model, metadata in MODEL_METADATA.items()
|
||||
]
|
||||
+ [
|
||||
BlockCost(
|
||||
cost_type=BlockCostType.RUN,
|
||||
cost_filter={
|
||||
"model": model,
|
||||
"credentials": {
|
||||
"id": anthropic_credentials.id,
|
||||
"provider": anthropic_credentials.provider,
|
||||
"type": anthropic_credentials.type,
|
||||
},
|
||||
},
|
||||
cost_amount=metadata.cost_factor,
|
||||
)
|
||||
for model, metadata in MODEL_METADATA.items()
|
||||
if metadata.provider == "anthropic"
|
||||
]
|
||||
+ [
|
||||
BlockCost(
|
||||
cost_type=BlockCostType.RUN,
|
||||
cost_filter={
|
||||
"model": model,
|
||||
"credentials": {
|
||||
"id": openai_credentials.id,
|
||||
"provider": openai_credentials.provider,
|
||||
"type": openai_credentials.type,
|
||||
},
|
||||
},
|
||||
cost_amount=metadata.cost_factor,
|
||||
)
|
||||
for model, metadata in MODEL_METADATA.items()
|
||||
if metadata.provider == "openai"
|
||||
]
|
||||
+ [
|
||||
BlockCost(
|
||||
cost_type=BlockCostType.RUN,
|
||||
cost_filter={
|
||||
"model": model,
|
||||
"credentials": {"id": groq_credentials.id},
|
||||
},
|
||||
cost_amount=metadata.cost_factor,
|
||||
)
|
||||
for model, metadata in MODEL_METADATA.items()
|
||||
if metadata.provider == "groq"
|
||||
]
|
||||
+ [
|
||||
BlockCost(
|
||||
# Default cost is running LlmModel.GPT4O.
|
||||
cost_amount=MODEL_METADATA[LlmModel.GPT4O].cost_factor,
|
||||
cost_filter={"api_key": None},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
|
||||
AIConversationBlock: llm_cost,
|
||||
@@ -73,12 +132,57 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
|
||||
AIStructuredResponseGeneratorBlock: llm_cost,
|
||||
AITextSummarizerBlock: llm_cost,
|
||||
CreateTalkingAvatarVideoBlock: [
|
||||
BlockCost(cost_amount=15, cost_filter={"api_key": None})
|
||||
BlockCost(
|
||||
cost_amount=15,
|
||||
cost_filter={
|
||||
"credentials": {
|
||||
"id": did_credentials.id,
|
||||
"provider": did_credentials.provider,
|
||||
"type": did_credentials.type,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
SearchTheWebBlock: [BlockCost(cost_amount=1)],
|
||||
ExtractWebsiteContentBlock: [
|
||||
BlockCost(cost_amount=1, cost_filter={"raw_content": False})
|
||||
],
|
||||
IdeogramModelBlock: [
|
||||
BlockCost(
|
||||
cost_amount=1,
|
||||
cost_filter={
|
||||
"credentials": {
|
||||
"id": ideogram_credentials.id,
|
||||
"provider": ideogram_credentials.provider,
|
||||
"type": ideogram_credentials.type,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
AIShortformVideoCreatorBlock: [
|
||||
BlockCost(
|
||||
cost_amount=10,
|
||||
cost_filter={
|
||||
"credentials": {
|
||||
"id": revid_credentials.id,
|
||||
"provider": revid_credentials.provider,
|
||||
"type": revid_credentials.type,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
ReplicateFluxAdvancedModelBlock: [
|
||||
BlockCost(
|
||||
cost_amount=10,
|
||||
cost_filter={
|
||||
"credentials": {
|
||||
"id": replicate_credentials.id,
|
||||
"provider": replicate_credentials.provider,
|
||||
"type": replicate_credentials.type,
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
from typing import Optional
|
||||
import logging
|
||||
from typing import Optional, cast
|
||||
|
||||
from autogpt_libs.supabase_integration_credentials_store.types import UserMetadataRaw
|
||||
from autogpt_libs.supabase_integration_credentials_store.types import (
|
||||
UserIntegrations,
|
||||
UserMetadata,
|
||||
UserMetadataRaw,
|
||||
)
|
||||
from fastapi import HTTPException
|
||||
from prisma import Json
|
||||
from prisma.models import User
|
||||
|
||||
from backend.data.db import prisma
|
||||
from backend.util.encryption import JSONCryptor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_USER_ID = "3e53486c-cf57-477e-ba2a-cb02dc828e1a"
|
||||
DEFAULT_EMAIL = "default@example.com"
|
||||
@@ -50,19 +58,79 @@ async def create_default_user() -> Optional[User]:
|
||||
return User.model_validate(user)
|
||||
|
||||
|
||||
async def get_user_metadata(user_id: str) -> UserMetadataRaw:
|
||||
async def get_user_metadata(user_id: str) -> UserMetadata:
|
||||
user = await User.prisma().find_unique_or_raise(
|
||||
where={"id": user_id},
|
||||
)
|
||||
return (
|
||||
UserMetadataRaw.model_validate(user.metadata)
|
||||
if user.metadata
|
||||
else UserMetadataRaw()
|
||||
)
|
||||
|
||||
metadata = cast(UserMetadataRaw, user.metadata)
|
||||
return UserMetadata.model_validate(metadata)
|
||||
|
||||
|
||||
async def update_user_metadata(user_id: str, metadata: UserMetadataRaw):
|
||||
async def update_user_metadata(user_id: str, metadata: UserMetadata):
|
||||
await User.prisma().update(
|
||||
where={"id": user_id},
|
||||
data={"metadata": Json(metadata.model_dump())},
|
||||
)
|
||||
|
||||
|
||||
async def get_user_integrations(user_id: str) -> UserIntegrations:
|
||||
user = await User.prisma().find_unique_or_raise(
|
||||
where={"id": user_id},
|
||||
)
|
||||
|
||||
encrypted_integrations = user.integrations
|
||||
if not encrypted_integrations:
|
||||
return UserIntegrations()
|
||||
else:
|
||||
return UserIntegrations.model_validate(
|
||||
JSONCryptor().decrypt(encrypted_integrations)
|
||||
)
|
||||
|
||||
|
||||
async def update_user_integrations(user_id: str, data: UserIntegrations):
|
||||
encrypted_data = JSONCryptor().encrypt(data.model_dump())
|
||||
await User.prisma().update(
|
||||
where={"id": user_id},
|
||||
data={"integrations": encrypted_data},
|
||||
)
|
||||
|
||||
|
||||
async def migrate_and_encrypt_user_integrations():
|
||||
"""Migrate integration credentials and OAuth states from metadata to integrations column."""
|
||||
users = await User.prisma().find_many(
|
||||
where={
|
||||
"metadata": {
|
||||
"path": ["integration_credentials"],
|
||||
"not": Json({"a": "yolo"}), # bogus value works to check if key exists
|
||||
} # type: ignore
|
||||
}
|
||||
)
|
||||
logger.info(f"Migrating integration credentials for {len(users)} users")
|
||||
|
||||
for user in users:
|
||||
raw_metadata = cast(UserMetadataRaw, user.metadata)
|
||||
metadata = UserMetadata.model_validate(raw_metadata)
|
||||
|
||||
# Get existing integrations data
|
||||
integrations = await get_user_integrations(user_id=user.id)
|
||||
|
||||
# Copy credentials and oauth states from metadata if they exist
|
||||
if metadata.integration_credentials and not integrations.credentials:
|
||||
integrations.credentials = metadata.integration_credentials
|
||||
if metadata.integration_oauth_states:
|
||||
integrations.oauth_states = metadata.integration_oauth_states
|
||||
|
||||
# Save to integrations column
|
||||
await update_user_integrations(user_id=user.id, data=integrations)
|
||||
|
||||
# Remove from metadata
|
||||
raw_metadata = dict(raw_metadata)
|
||||
raw_metadata.pop("integration_credentials", None)
|
||||
raw_metadata.pop("integration_oauth_states", None)
|
||||
|
||||
# Update metadata without integration data
|
||||
await User.prisma().update(
|
||||
where={"id": user.id},
|
||||
data={"metadata": Json(raw_metadata)},
|
||||
)
|
||||
|
||||
@@ -16,7 +16,12 @@ from backend.data.execution import (
|
||||
)
|
||||
from backend.data.graph import get_graph, get_node
|
||||
from backend.data.queue import RedisExecutionEventBus
|
||||
from backend.data.user import get_user_metadata, update_user_metadata
|
||||
from backend.data.user import (
|
||||
get_user_integrations,
|
||||
get_user_metadata,
|
||||
update_user_integrations,
|
||||
update_user_metadata,
|
||||
)
|
||||
from backend.util.service import AppService, expose
|
||||
from backend.util.settings import Config
|
||||
|
||||
@@ -25,7 +30,6 @@ R = TypeVar("R")
|
||||
|
||||
|
||||
class DatabaseManager(AppService):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.use_db = True
|
||||
@@ -79,6 +83,8 @@ class DatabaseManager(AppService):
|
||||
exposed_run_and_wait(user_credit_model.spend_credits),
|
||||
)
|
||||
|
||||
# User + User Metadata
|
||||
# User + User Metadata + User Integrations
|
||||
get_user_metadata = exposed_run_and_wait(get_user_metadata)
|
||||
update_user_metadata = exposed_run_and_wait(update_user_metadata)
|
||||
get_user_integrations = exposed_run_and_wait(get_user_integrations)
|
||||
update_user_integrations = exposed_run_and_wait(update_user_integrations)
|
||||
|
||||
@@ -19,7 +19,7 @@ from backend.data import execution as execution_db
|
||||
from backend.data import graph as graph_db
|
||||
from backend.data.block import BlockInput, CompletedBlockOutput
|
||||
from backend.data.credit import get_block_costs, get_user_credit_model
|
||||
from backend.data.user import get_or_create_user
|
||||
from backend.data.user import get_or_create_user, migrate_and_encrypt_user_integrations
|
||||
from backend.executor import ExecutionManager, ExecutionScheduler
|
||||
from backend.server.model import CreateGraph, SetGraphActiveVersion
|
||||
from backend.util.service import AppService, get_service_client
|
||||
@@ -47,6 +47,7 @@ class AgentServer(AppService):
|
||||
async def lifespan(self, _: FastAPI):
|
||||
await db.connect()
|
||||
await block.initialize_blocks()
|
||||
await migrate_and_encrypt_user_integrations()
|
||||
yield
|
||||
await db.disconnect()
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ and it should be enclosed within this block format:
|
||||
|
||||
Here are a couple of sample of the Block class implementation:
|
||||
|
||||
{"--------------\n".join([sample_block_codes[v] for v in sample_block_modules])}
|
||||
{"--------------".join([sample_block_codes[v] for v in sample_block_modules])}
|
||||
""",
|
||||
},
|
||||
)
|
||||
|
||||
34
autogpt_platform/backend/backend/util/encryption.py
Normal file
34
autogpt_platform/backend/backend/util/encryption.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from backend.util.settings import Settings
|
||||
|
||||
ENCRYPTION_KEY = Settings().secrets.encryption_key
|
||||
|
||||
|
||||
class JSONCryptor:
|
||||
def __init__(self, key: Optional[str] = None):
|
||||
# Use provided key or get from environment
|
||||
self.key = key or ENCRYPTION_KEY
|
||||
if not self.key:
|
||||
raise ValueError(
|
||||
"Encryption key must be provided or set in ENCRYPTION_KEY environment variable"
|
||||
)
|
||||
self.fernet = Fernet(
|
||||
self.key.encode() if isinstance(self.key, str) else self.key
|
||||
)
|
||||
|
||||
def encrypt(self, data: dict) -> str:
|
||||
"""Encrypt dictionary data to string"""
|
||||
json_str = json.dumps(data)
|
||||
encrypted = self.fernet.encrypt(json_str.encode())
|
||||
return encrypted.decode()
|
||||
|
||||
def decrypt(self, encrypted_str: str) -> dict:
|
||||
"""Decrypt string to dictionary"""
|
||||
if not encrypted_str:
|
||||
return {}
|
||||
decrypted = self.fernet.decrypt(encrypted_str.encode())
|
||||
return json.loads(decrypted.decode())
|
||||
@@ -208,6 +208,8 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
||||
default="", description="Supabase service role key"
|
||||
)
|
||||
|
||||
encryption_key: str = Field(default="", description="Encryption key")
|
||||
|
||||
# OAuth server credentials for integrations
|
||||
# --8<-- [start:OAuthServerCredentialsExample]
|
||||
github_client_id: str = Field(default="", description="GitHub OAuth client ID")
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
-- Migrate integration credentials from auth.user.raw_user_meta_data to platform.User.metadata
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name = 'users'
|
||||
) THEN
|
||||
-- First update User metadata for users that have integration_credentials
|
||||
WITH users_with_creds AS (
|
||||
SELECT
|
||||
id,
|
||||
raw_user_meta_data->'integration_credentials' as integration_credentials,
|
||||
raw_user_meta_data
|
||||
FROM auth.users
|
||||
WHERE raw_user_meta_data ? 'integration_credentials'
|
||||
)
|
||||
UPDATE "User" u
|
||||
SET metadata = COALESCE(
|
||||
CASE
|
||||
-- If User.metadata already has .integration_credentials, leave it
|
||||
WHEN u.metadata ? 'integration_credentials' THEN u.metadata
|
||||
-- If User.metadata exists but has no .integration_credentials, add it
|
||||
WHEN u.metadata IS NOT NULL AND u.metadata::text != '' THEN
|
||||
(u.metadata || jsonb_build_object('integration_credentials', uwc.integration_credentials))
|
||||
-- If User.metadata is NULL, set it
|
||||
ELSE jsonb_build_object('integration_credentials', uwc.integration_credentials)
|
||||
END,
|
||||
'{}'::jsonb
|
||||
)
|
||||
FROM users_with_creds uwc
|
||||
WHERE u.id = uwc.id::text;
|
||||
|
||||
-- Finally remove integration_credentials from auth.users
|
||||
UPDATE auth.users
|
||||
SET raw_user_meta_data = raw_user_meta_data - 'integration_credentials'
|
||||
WHERE raw_user_meta_data ? 'integration_credentials';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Make User.metadata column consistent and add integrations column for encrypted credentials
|
||||
|
||||
-- First update all records to have empty JSON object
|
||||
UPDATE "User"
|
||||
SET "metadata" = '{}'::jsonb
|
||||
WHERE "metadata" IS NULL;
|
||||
|
||||
-- Then make it required
|
||||
ALTER TABLE "User"
|
||||
ALTER COLUMN "metadata" SET DEFAULT '{}'::jsonb,
|
||||
ALTER COLUMN "metadata" SET NOT NULL,
|
||||
-- and add integrations column (which will be encrypted JSON)
|
||||
ADD COLUMN "integrations" TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- Encrypting the credentials and moving them from metadata to integrations
|
||||
-- will be handled in the backend.
|
||||
@@ -0,0 +1,59 @@
|
||||
-- Function to clean sensitive data from JSON
|
||||
CREATE OR REPLACE FUNCTION clean_sensitive_json(data jsonb)
|
||||
RETURNS jsonb AS $$
|
||||
DECLARE
|
||||
result jsonb := data;
|
||||
BEGIN
|
||||
-- If the JSON contains api_key directly
|
||||
IF result ? 'api_key' THEN
|
||||
result = result - 'api_key';
|
||||
END IF;
|
||||
|
||||
-- If the JSON contains discord_bot_token
|
||||
IF result ? 'discord_bot_token' THEN
|
||||
result = result - 'discord_bot_token';
|
||||
END IF;
|
||||
|
||||
-- If the JSON contains creds
|
||||
IF result ? 'creds' THEN
|
||||
result = result - 'creds';
|
||||
END IF;
|
||||
|
||||
-- If the JSON contains smtp credentials
|
||||
IF result ? 'smtp_username' THEN
|
||||
result = result - 'smtp_username';
|
||||
END IF;
|
||||
|
||||
IF result ? 'smtp_password' THEN
|
||||
result = result - 'smtp_password';
|
||||
END IF;
|
||||
|
||||
-- If the JSON contains OAuth credentials
|
||||
IF result ? 'client_id' THEN
|
||||
result = result - 'client_id';
|
||||
END IF;
|
||||
|
||||
IF result ? 'client_secret' THEN
|
||||
result = result - 'client_secret';
|
||||
END IF;
|
||||
|
||||
-- If the JSON contains username/password
|
||||
IF result ? 'username' THEN
|
||||
result = result - 'username';
|
||||
END IF;
|
||||
|
||||
IF result ? 'password' THEN
|
||||
result = result - 'password';
|
||||
END IF;
|
||||
|
||||
RETURN result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Update the table using the function
|
||||
UPDATE "AgentNode"
|
||||
SET "constantInput" = clean_sensitive_json("constantInput"::jsonb)::json
|
||||
WHERE "constantInput"::jsonb ?| array['api_key', 'discord_bot_token', 'creds', 'smtp_username', 'smtp_password', 'client_id', 'client_secret', 'username', 'password'];
|
||||
|
||||
-- Drop the function after use
|
||||
DROP FUNCTION clean_sensitive_json;
|
||||
219
autogpt_platform/backend/poetry.lock
generated
219
autogpt_platform/backend/poetry.lock
generated
@@ -294,12 +294,12 @@ develop = true
|
||||
[package.dependencies]
|
||||
colorama = "^0.4.6"
|
||||
expiringdict = "^1.2.2"
|
||||
google-cloud-logging = "^3.8.0"
|
||||
pydantic = "^2.8.2"
|
||||
pydantic-settings = "^2.5.2"
|
||||
google-cloud-logging = "^3.11.3"
|
||||
pydantic = "^2.9.2"
|
||||
pydantic-settings = "^2.6.0"
|
||||
pyjwt = "^2.8.0"
|
||||
python-dotenv = "^1.0.1"
|
||||
supabase = "^2.7.2"
|
||||
supabase = "^2.9.1"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -373,6 +373,85 @@ files = [
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
|
||||
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
@@ -499,19 +578,68 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "croniter"
|
||||
version = "2.0.7"
|
||||
version = "5.0.1"
|
||||
description = "croniter provides iteration for datetime object with cron like format"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.6"
|
||||
files = [
|
||||
{file = "croniter-2.0.7-py2.py3-none-any.whl", hash = "sha256:f15e80828d23920c4bb7f4d9340b932c9dcabecafc7775703c8b36d1253ed526"},
|
||||
{file = "croniter-2.0.7.tar.gz", hash = "sha256:1041b912b4b1e03751a0993531becf77851ae6e8b334c9c76ffeffb8f055f53f"},
|
||||
{file = "croniter-5.0.1-py2.py3-none-any.whl", hash = "sha256:eb28439742291f6c10b181df1a5ecf421208b1fc62ef44501daec1780a0b09e9"},
|
||||
{file = "croniter-5.0.1.tar.gz", hash = "sha256:7d9b1ef25b10eece48fdf29d8ac52f9b6252abff983ac614ade4f3276294019e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = "*"
|
||||
pytz = ">2021.1"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "43.0.3"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"},
|
||||
{file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"},
|
||||
{file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"},
|
||||
{file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"},
|
||||
{file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"},
|
||||
{file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
nox = ["nox"]
|
||||
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||
sdist = ["build"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
name = "deprecated"
|
||||
version = "1.2.14"
|
||||
@@ -949,13 +1077,13 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"]
|
||||
|
||||
[[package]]
|
||||
name = "google-cloud-logging"
|
||||
version = "3.11.2"
|
||||
version = "3.11.3"
|
||||
description = "Stackdriver Logging API client library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google_cloud_logging-3.11.2-py2.py3-none-any.whl", hash = "sha256:0a755f04f184fbe77ad608258dc283a032485ebb4d0e2b2501964059ee9c898f"},
|
||||
{file = "google_cloud_logging-3.11.2.tar.gz", hash = "sha256:4897441c2b74f6eda9181c23a8817223b6145943314a821d64b729d30766cb2b"},
|
||||
{file = "google_cloud_logging-3.11.3-py2.py3-none-any.whl", hash = "sha256:b8ec23f2998f76a58f8492db26a0f4151dd500425c3f08448586b85972f3c494"},
|
||||
{file = "google_cloud_logging-3.11.3.tar.gz", hash = "sha256:0a73cd94118875387d4535371d9e9426861edef8e44fba1261e86782d5b8d54f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1860,8 +1988,8 @@ python-dateutil = ">=2.5.3"
|
||||
tqdm = ">=4.64.1"
|
||||
typing-extensions = ">=3.7.4"
|
||||
urllib3 = [
|
||||
{version = ">=1.26.5", markers = "python_version >= \"3.12\" and python_version < \"4.0\""},
|
||||
{version = ">=1.26.0", markers = "python_version >= \"3.8\" and python_version < \"3.12\""},
|
||||
{version = ">=1.26.5", markers = "python_version >= \"3.12\" and python_version < \"4.0\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1944,20 +2072,20 @@ poetry-plugin = ["poetry (>=1.0,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "postgrest"
|
||||
version = "0.16.11"
|
||||
version = "0.17.2"
|
||||
description = "PostgREST client for Python. This library provides an ORM interface to PostgREST."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "postgrest-0.16.11-py3-none-any.whl", hash = "sha256:22fb6b817ace1f68aa648fd4ce0f56d2786c9260fa4ed2cb9046191231a682b8"},
|
||||
{file = "postgrest-0.16.11.tar.gz", hash = "sha256:10af51b4c39e288ad7df2db92d6a61fb3c4683131b40561f473e3de116e83fa5"},
|
||||
{file = "postgrest-0.17.2-py3-none-any.whl", hash = "sha256:f7c4f448e5a5e2d4c1dcf192edae9d1007c4261e9a6fb5116783a0046846ece2"},
|
||||
{file = "postgrest-0.17.2.tar.gz", hash = "sha256:445cd4e4a191e279492549df0c4e827d32f9d01d0852599bb8a6efb0f07fcf78"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
deprecation = ">=2.1.0,<3.0.0"
|
||||
httpx = {version = ">=0.24,<0.28", extras = ["http2"]}
|
||||
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
|
||||
pydantic = ">=1.9,<3.0"
|
||||
strenum = ">=0.4.9,<0.5.0"
|
||||
strenum = {version = ">=0.4.9,<0.5.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "praw"
|
||||
@@ -2131,6 +2259,17 @@ files = [
|
||||
{file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.9.2"
|
||||
@@ -2146,8 +2285,8 @@ files = [
|
||||
annotated-types = ">=0.6.0"
|
||||
pydantic-core = "2.23.4"
|
||||
typing-extensions = [
|
||||
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
|
||||
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -2257,13 +2396,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.5.2"
|
||||
version = "2.6.0"
|
||||
description = "Settings management using Pydantic"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"},
|
||||
{file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"},
|
||||
{file = "pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0"},
|
||||
{file = "pydantic_settings-2.6.0.tar.gz", hash = "sha256:44a1804abffac9e6a30372bb45f6cafab945ef5af25e66b1c634c01dd39e0188"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2880,17 +3019,17 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7
|
||||
|
||||
[[package]]
|
||||
name = "storage3"
|
||||
version = "0.7.7"
|
||||
version = "0.8.2"
|
||||
description = "Supabase Storage client for Python."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "storage3-0.7.7-py3-none-any.whl", hash = "sha256:ed80a2546cd0b5c22e2c30ea71096db6c99268daf2958c603488e7d72efb8426"},
|
||||
{file = "storage3-0.7.7.tar.gz", hash = "sha256:9fba680cf761d139ad764f43f0e91c245d1ce1af2cc3afe716652f835f48f83e"},
|
||||
{file = "storage3-0.8.2-py3-none-any.whl", hash = "sha256:f2e995b18c77a2a9265d1a33047d43e4d6abb11eb3ca5067959f68281c305de3"},
|
||||
{file = "storage3-0.8.2.tar.gz", hash = "sha256:db05d3fe8fb73bd30c814c4c4749664f37a5dfc78b629e8c058ef558c2b89f5a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
httpx = {version = ">=0.24,<0.28", extras = ["http2"]}
|
||||
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
|
||||
python-dateutil = ">=2.8.2,<3.0.0"
|
||||
typing-extensions = ">=4.2.0,<5.0.0"
|
||||
|
||||
@@ -2912,36 +3051,36 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
|
||||
|
||||
[[package]]
|
||||
name = "supabase"
|
||||
version = "2.7.4"
|
||||
version = "2.9.1"
|
||||
description = "Supabase client for Python."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "supabase-2.7.4-py3-none-any.whl", hash = "sha256:01815fbc30cac753933d4a44a2529fd13cb7634b56c705c65b12a02c8e75982b"},
|
||||
{file = "supabase-2.7.4.tar.gz", hash = "sha256:5a979c7711b3c5ce688514fa0afc015780522569494e1a9a9d25d03b7c3d654b"},
|
||||
{file = "supabase-2.9.1-py3-none-any.whl", hash = "sha256:a96f857a465712cb551679c1df66ba772c834f861756ce4aa2aa4cb703f6aeb7"},
|
||||
{file = "supabase-2.9.1.tar.gz", hash = "sha256:51fce39c9eb50573126dabb342541ec5e1f13e7476938768f4b0ccfdb8c522cd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
gotrue = ">=1.3,<3.0"
|
||||
httpx = ">=0.24,<0.28"
|
||||
postgrest = ">=0.14,<0.17.0"
|
||||
gotrue = ">=2.9.0,<3.0.0"
|
||||
httpx = ">=0.26,<0.28"
|
||||
postgrest = ">=0.17.0,<0.18.0"
|
||||
realtime = ">=2.0.0,<3.0.0"
|
||||
storage3 = ">=0.5.3,<0.8.0"
|
||||
supafunc = ">=0.3.1,<0.6.0"
|
||||
storage3 = ">=0.8.0,<0.9.0"
|
||||
supafunc = ">=0.6.0,<0.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "supafunc"
|
||||
version = "0.5.1"
|
||||
version = "0.6.2"
|
||||
description = "Library for Supabase Functions"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "supafunc-0.5.1-py3-none-any.whl", hash = "sha256:b05e99a2b41270211a3f90ec843c04c5f27a5618f2d2d2eb8e07f41eb962a910"},
|
||||
{file = "supafunc-0.5.1.tar.gz", hash = "sha256:1ae9dce6bd935939c561650e86abb676af9665ecf5d4ffc1c7ec3c4932c84334"},
|
||||
{file = "supafunc-0.6.2-py3-none-any.whl", hash = "sha256:101b30616b0a1ce8cf938eca1df362fa4cf1deacb0271f53ebbd674190fb0da5"},
|
||||
{file = "supafunc-0.6.2.tar.gz", hash = "sha256:c7dfa20db7182f7fe4ae436e94e05c06cd7ed98d697fed75d68c7b9792822adc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
httpx = {version = ">=0.24,<0.28", extras = ["http2"]}
|
||||
httpx = {version = ">=0.26,<0.28", extras = ["http2"]}
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
@@ -3741,4 +3880,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "ab3ae697e0be22e3ed20ae136db5b6805086279ebb57c99b60ed1c4d8d2dbbae"
|
||||
content-hash = "00069717c4818aa24b164e3c00a104d559c2fb16c531f60bccfcf5a69fb553c8"
|
||||
|
||||
@@ -14,7 +14,7 @@ anthropic = "^0.25.1"
|
||||
apscheduler = "^3.10.4"
|
||||
autogpt-libs = { path = "../autogpt_libs", develop = true }
|
||||
click = "^8.1.7"
|
||||
croniter = "^2.0.5"
|
||||
croniter = "^5.0.1"
|
||||
discord-py = "^2.4.0"
|
||||
fastapi = "^0.109.0"
|
||||
feedparser = "^6.0.11"
|
||||
@@ -46,6 +46,7 @@ youtube-transcript-api = "^0.6.2"
|
||||
googlemaps = "^4.10.0"
|
||||
replicate = "^0.34.1"
|
||||
pinecone = "^5.3.1"
|
||||
cryptography = "^43.0.3"
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
poethepoet = "^0.29.0"
|
||||
httpx = "^0.27.0"
|
||||
|
||||
@@ -12,12 +12,13 @@ generator client {
|
||||
|
||||
// User model to mirror Auth provider users
|
||||
model User {
|
||||
id String @id // This should match the Supabase user ID
|
||||
email String @unique
|
||||
name String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
metadata Json?
|
||||
id String @id // This should match the Supabase user ID
|
||||
email String @unique
|
||||
name String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
metadata Json @default("{}")
|
||||
integrations String @default("")
|
||||
|
||||
// Relations
|
||||
AgentGraphs AgentGraph[]
|
||||
|
||||
@@ -21,7 +21,7 @@ model User {
|
||||
name String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
metadata Json? @default("{}")
|
||||
metadata String @default("")
|
||||
|
||||
// Relations
|
||||
Agents Agent[]
|
||||
|
||||
7
autogpt_platform/frontend/.env.development
Normal file
7
autogpt_platform/frontend/.env.development
Normal file
@@ -0,0 +1,7 @@
|
||||
NEXT_PUBLIC_AUTH_CALLBACK_URL=https://dev-server.agpt.co/auth/callback
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL=https://dev-server.agpt.co/api
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL=wss://dev-ws-server.agpt.co/ws
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL=https://dev-market.agpt.co/api/v1/market
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFkZmp0ZXh0a3VpbHd1aHpkanBmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzAyNTE3MDIsImV4cCI6MjA0NTgyNzcwMn0.IuQNXsHEKJNxtS9nyFeqO0BGMYN8sPiObQhuJLSK9xk"
|
||||
NEXT_PUBLIC_SUPABASE_URL="https://adfjtextkuilwuhzdjpf.supabase.co"
|
||||
NEXT_PUBLIC_BEHAVE_AS=CLOUD
|
||||
8
autogpt_platform/frontend/.env.production
Normal file
8
autogpt_platform/frontend/.env.production
Normal file
@@ -0,0 +1,8 @@
|
||||
APP_ENV=production
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL=https://market.agpt.co/api/v1/market
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL=https://backend.agpt.co/api
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL=wss://ws-backend.agpt.co/ws
|
||||
NEXT_PUBLIC_AUTH_CALLBACK_URL=https://backend.agpt.co/auth/callback
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJnd3B3ZHN4YmxyeWloaW51dGJ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzAyODYzMDUsImV4cCI6MjA0NTg2MjMwNX0.ISa2IofTdQIJmmX5JwKGGNajqjsD8bjaGBzK90SubE0
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://bgwpwdsxblryihinutbx.supabase.co
|
||||
NEXT_PUBLIC_BEHAVE_AS=CLOUD
|
||||
@@ -23,29 +23,29 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.5.5",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@next/third-parties": "^14.2.5",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-collapsible": "^1.1.0",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@next/third-parties": "^15.0.2",
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.1",
|
||||
"@radix-ui/react-context-menu": "^2.2.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-popover": "^1.1.2",
|
||||
"@radix-ui/react-scroll-area": "^1.2.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.1",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@sentry/nextjs": "^8",
|
||||
"@supabase/ssr": "^0.4.0",
|
||||
"@supabase/supabase-js": "^2.45.0",
|
||||
"@supabase/ssr": "^0.5.1",
|
||||
"@supabase/supabase-js": "^2.46.0",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@xyflow/react": "^12.3.1",
|
||||
"@xyflow/react": "^12.3.2",
|
||||
"ajv": "^8.17.1",
|
||||
"boring-avatars": "^1.11.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@@ -55,39 +55,40 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"framer-motion": "^11.11.9",
|
||||
"lucide-react": "^0.407.0",
|
||||
"lucide-react": "^0.454.0",
|
||||
"moment": "^2.30.1",
|
||||
"negotiator": "^1.0.0",
|
||||
"next": "^14.2.13",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^9.2.0",
|
||||
"react-day-picker": "^9.2.1",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-shepherd": "^6.1.1",
|
||||
"recharts": "^2.12.7",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"react-shepherd": "^6.1.4",
|
||||
"recharts": "^2.13.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.47.1",
|
||||
"@chromatic-com/storybook": "^1.9.0",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"@storybook/addon-a11y": "^8.3.5",
|
||||
"@storybook/addon-essentials": "^8.3.5",
|
||||
"@storybook/addon-interactions": "^8.3.5",
|
||||
"@storybook/addon-links": "^8.3.5",
|
||||
"@storybook/addon-onboarding": "^8.3.5",
|
||||
"@storybook/blocks": "^8.3.5",
|
||||
"@storybook/nextjs": "^8.3.5",
|
||||
"@storybook/addon-essentials": "^8.3.6",
|
||||
"@storybook/addon-interactions": "^8.3.6",
|
||||
"@storybook/addon-links": "^8.3.6",
|
||||
"@storybook/addon-onboarding": "^8.3.6",
|
||||
"@storybook/blocks": "^8.3.6",
|
||||
"@storybook/nextjs": "^8.3.6",
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@storybook/test-runner": "^0.19.1",
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "^22.7.3",
|
||||
"@types/node": "^22.8.4",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
@@ -95,13 +96,13 @@
|
||||
"chromatic": "^11.12.5",
|
||||
"concurrently": "^9.0.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.4",
|
||||
"eslint-plugin-storybook": "^0.9.0",
|
||||
"eslint-config-next": "15.0.2",
|
||||
"eslint-plugin-storybook": "^0.10.1",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"storybook": "^8.3.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"storybook": "^8.3.6",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
|
||||
@@ -80,7 +80,7 @@ export const SaveControl = ({
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [handleSave]);
|
||||
}, [handleSave, toast]);
|
||||
|
||||
return (
|
||||
<Popover open={pinSavePopover ? true : undefined}>
|
||||
|
||||
@@ -7,9 +7,12 @@ import useCredentials from "@/hooks/useCredentials";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { NotionLogoIcon } from "@radix-ui/react-icons";
|
||||
import { FaGithub, FaGoogle, FaKey } from "react-icons/fa";
|
||||
import { FaDiscord, FaGithub, FaGoogle, FaMedium, FaKey } from "react-icons/fa";
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||
import {
|
||||
CredentialsMetaInput,
|
||||
CredentialsProviderName,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { IconKey, IconKeyPlus, IconUserPlus } from "@/components/ui/icons";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -36,13 +39,29 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
const fallbackIcon = FaKey;
|
||||
|
||||
// --8<-- [start:ProviderIconsEmbed]
|
||||
export const providerIcons: Record<string, React.FC<{ className?: string }>> = {
|
||||
export const providerIcons: Record<
|
||||
CredentialsProviderName,
|
||||
React.FC<{ className?: string }>
|
||||
> = {
|
||||
github: FaGithub,
|
||||
google: FaGoogle,
|
||||
notion: NotionLogoIcon,
|
||||
jina: FaKey,
|
||||
pinecone: FaKey,
|
||||
discord: FaDiscord,
|
||||
d_id: fallbackIcon,
|
||||
google_maps: FaGoogle,
|
||||
jina: fallbackIcon,
|
||||
ideogram: fallbackIcon,
|
||||
llm: fallbackIcon,
|
||||
medium: FaMedium,
|
||||
openai: fallbackIcon,
|
||||
openweathermap: fallbackIcon,
|
||||
pinecone: fallbackIcon,
|
||||
replicate: fallbackIcon,
|
||||
revid: fallbackIcon,
|
||||
unreal_speech: fallbackIcon,
|
||||
};
|
||||
// --8<-- [end:ProviderIconsEmbed]
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import AutoGPTServerAPI, {
|
||||
APIKeyCredentials,
|
||||
CredentialsDeleteResponse,
|
||||
CredentialsMetaResponse,
|
||||
CredentialsProviderName,
|
||||
PROVIDER_NAMES,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import {
|
||||
createContext,
|
||||
@@ -11,25 +13,30 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
// Get keys from CredentialsProviderName type
|
||||
const CREDENTIALS_PROVIDER_NAMES = Object.values(
|
||||
PROVIDER_NAMES,
|
||||
) as CredentialsProviderName[];
|
||||
|
||||
// --8<-- [start:CredentialsProviderNames]
|
||||
const CREDENTIALS_PROVIDER_NAMES = [
|
||||
"github",
|
||||
"google",
|
||||
"notion",
|
||||
"jina",
|
||||
"pinecone",
|
||||
] as const;
|
||||
|
||||
export type CredentialsProviderName =
|
||||
(typeof CREDENTIALS_PROVIDER_NAMES)[number];
|
||||
|
||||
const providerDisplayNames: Record<CredentialsProviderName, string> = {
|
||||
discord: "Discord",
|
||||
d_id: "D-ID",
|
||||
github: "GitHub",
|
||||
google: "Google",
|
||||
notion: "Notion",
|
||||
google_maps: "Google Maps",
|
||||
ideogram: "Ideogram",
|
||||
jina: "Jina",
|
||||
medium: "Medium",
|
||||
llm: "LLM",
|
||||
notion: "Notion",
|
||||
openai: "OpenAI",
|
||||
openweathermap: "OpenWeatherMap",
|
||||
pinecone: "Pinecone",
|
||||
};
|
||||
replicate: "Replicate",
|
||||
revid: "Rev.ID",
|
||||
unreal_speech: "Unreal Speech",
|
||||
} as const;
|
||||
// --8<-- [end:CredentialsProviderNames]
|
||||
|
||||
type APIKeyCredentialsCreatable = Omit<
|
||||
@@ -162,41 +169,43 @@ export default function CredentialsProvider({
|
||||
api.isAuthenticated().then((isAuthenticated) => {
|
||||
if (!isAuthenticated) return;
|
||||
|
||||
CREDENTIALS_PROVIDER_NAMES.forEach((provider) => {
|
||||
api.listCredentials(provider).then((response) => {
|
||||
const { oauthCreds, apiKeys } = response.reduce<{
|
||||
oauthCreds: CredentialsMetaResponse[];
|
||||
apiKeys: CredentialsMetaResponse[];
|
||||
}>(
|
||||
(acc, cred) => {
|
||||
if (cred.type === "oauth2") {
|
||||
acc.oauthCreds.push(cred);
|
||||
} else if (cred.type === "api_key") {
|
||||
acc.apiKeys.push(cred);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ oauthCreds: [], apiKeys: [] },
|
||||
);
|
||||
CREDENTIALS_PROVIDER_NAMES.forEach(
|
||||
(provider: CredentialsProviderName) => {
|
||||
api.listCredentials(provider).then((response) => {
|
||||
const { oauthCreds, apiKeys } = response.reduce<{
|
||||
oauthCreds: CredentialsMetaResponse[];
|
||||
apiKeys: CredentialsMetaResponse[];
|
||||
}>(
|
||||
(acc, cred) => {
|
||||
if (cred.type === "oauth2") {
|
||||
acc.oauthCreds.push(cred);
|
||||
} else if (cred.type === "api_key") {
|
||||
acc.apiKeys.push(cred);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ oauthCreds: [], apiKeys: [] },
|
||||
);
|
||||
|
||||
setProviders((prev) => ({
|
||||
...prev,
|
||||
[provider]: {
|
||||
provider,
|
||||
providerName: providerDisplayNames[provider],
|
||||
savedApiKeys: apiKeys,
|
||||
savedOAuthCredentials: oauthCreds,
|
||||
oAuthCallback: (code: string, state_token: string) =>
|
||||
oAuthCallback(provider, code, state_token),
|
||||
createAPIKeyCredentials: (
|
||||
credentials: APIKeyCredentialsCreatable,
|
||||
) => createAPIKeyCredentials(provider, credentials),
|
||||
deleteCredentials: (id: string) =>
|
||||
deleteCredentials(provider, id),
|
||||
},
|
||||
}));
|
||||
});
|
||||
});
|
||||
setProviders((prev) => ({
|
||||
...prev,
|
||||
[provider]: {
|
||||
provider,
|
||||
providerName: providerDisplayNames[provider],
|
||||
savedApiKeys: apiKeys,
|
||||
savedOAuthCredentials: oauthCreds,
|
||||
oAuthCallback: (code: string, state_token: string) =>
|
||||
oAuthCallback(provider, code, state_token),
|
||||
createAPIKeyCredentials: (
|
||||
credentials: APIKeyCredentialsCreatable,
|
||||
) => createAPIKeyCredentials(provider, credentials),
|
||||
deleteCredentials: (id: string) =>
|
||||
deleteCredentials(provider, id),
|
||||
},
|
||||
}));
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}, [api, createAPIKeyCredentials, deleteCredentials, oAuthCallback]);
|
||||
|
||||
|
||||
@@ -10,76 +10,91 @@ import MarketplaceAPI from "@/lib/marketplace-api";
|
||||
import AutoGPTServerAPI, { GraphCreatable } from "@/lib/autogpt-server-api";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { makeAnalyticsEvent } from "./actions";
|
||||
|
||||
async function downloadAgent(id: string): Promise<void> {
|
||||
const api = new MarketplaceAPI();
|
||||
try {
|
||||
const file = await api.downloadAgentFile(id);
|
||||
console.debug(`Agent file downloaded:`, file);
|
||||
|
||||
// Create a Blob from the file content
|
||||
const blob = new Blob([file], { type: "application/json" });
|
||||
|
||||
// Create a temporary URL for the Blob
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `agent_${id}.json`; // Set the filename
|
||||
|
||||
// Append the anchor to the body, click it, and remove it
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Revoke the temporary URL
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading agent:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function installGraph(id: string): Promise<void> {
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
|
||||
"http://localhost:8015/api/v1/market";
|
||||
const api = new MarketplaceAPI(apiUrl);
|
||||
|
||||
const serverAPIUrl = process.env.NEXT_PUBLIC_AGPT_SERVER_API_URL;
|
||||
const serverAPI = new AutoGPTServerAPI(serverAPIUrl);
|
||||
try {
|
||||
console.debug(`Installing agent with id: ${id}`);
|
||||
let agent = await api.downloadAgent(id);
|
||||
console.debug(`Agent downloaded:`, agent);
|
||||
const data: GraphCreatable = {
|
||||
id: agent.id,
|
||||
version: agent.version,
|
||||
is_active: true,
|
||||
is_template: false,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
nodes: agent.graph.nodes,
|
||||
links: agent.graph.links,
|
||||
};
|
||||
const result = await serverAPI.createTemplate(data);
|
||||
makeAnalyticsEvent({
|
||||
event_name: "agent_installed_from_marketplace",
|
||||
event_data: {
|
||||
marketplace_agent_id: id,
|
||||
installed_agent_id: result.id,
|
||||
installation_location: InstallationLocation.CLOUD,
|
||||
},
|
||||
});
|
||||
console.debug(`Agent installed successfully`, result);
|
||||
} catch (error) {
|
||||
console.error(`Error installing agent:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) {
|
||||
const { toast } = useToast();
|
||||
|
||||
const downloadAgent = async (id: string): Promise<void> => {
|
||||
const api = new MarketplaceAPI();
|
||||
try {
|
||||
const file = await api.downloadAgentFile(id);
|
||||
console.debug(`Agent file downloaded:`, file);
|
||||
|
||||
// Create a Blob from the file content
|
||||
const blob = new Blob([file], { type: "application/json" });
|
||||
|
||||
// Create a temporary URL for the Blob
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `agent_${id}.json`; // Set the filename
|
||||
|
||||
// Append the anchor to the body, click it, and remove it
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Revoke the temporary URL
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading agent:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const installGraph = async (id: string): Promise<void> => {
|
||||
toast({
|
||||
title: "Saving and opening a new agent...",
|
||||
duration: 2000,
|
||||
});
|
||||
const apiUrl =
|
||||
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
|
||||
"http://localhost:8015/api/v1/market";
|
||||
const api = new MarketplaceAPI(apiUrl);
|
||||
|
||||
const serverAPIUrl = process.env.NEXT_PUBLIC_AGPT_SERVER_API_URL;
|
||||
const serverAPI = new AutoGPTServerAPI(serverAPIUrl);
|
||||
try {
|
||||
console.debug(`Installing agent with id: ${id}`);
|
||||
let agent = await api.downloadAgent(id);
|
||||
console.debug(`Agent downloaded:`, agent);
|
||||
const data: GraphCreatable = {
|
||||
id: agent.id,
|
||||
version: agent.version,
|
||||
is_active: true,
|
||||
is_template: false,
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
nodes: agent.graph.nodes,
|
||||
links: agent.graph.links,
|
||||
};
|
||||
const result = await serverAPI.createTemplate(data);
|
||||
makeAnalyticsEvent({
|
||||
event_name: "agent_installed_from_marketplace",
|
||||
event_data: {
|
||||
marketplace_agent_id: id,
|
||||
installed_agent_id: result.id,
|
||||
installation_location: InstallationLocation.CLOUD,
|
||||
},
|
||||
});
|
||||
console.debug(`Agent installed successfully`, result);
|
||||
serverAPI.createGraph(result.id, agent.version).then((newGraph) => {
|
||||
window.location.href = `/build?flowID=${newGraph.id}`;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error installing agent:`, error);
|
||||
toast({
|
||||
title: "Error saving template",
|
||||
variant: "destructive",
|
||||
duration: 2000,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
|
||||
@@ -88,7 +88,7 @@ export const Variants: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const buttons = canvas.getAllByRole("button");
|
||||
expect(buttons).toHaveLength(6);
|
||||
await expect(buttons).toHaveLength(6);
|
||||
for (const button of buttons) {
|
||||
await userEvent.hover(button);
|
||||
await expect(button).toHaveAttribute(
|
||||
@@ -122,7 +122,7 @@ export const Sizes: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const buttons = canvas.getAllByRole("button");
|
||||
expect(buttons).toHaveLength(5);
|
||||
await expect(buttons).toHaveLength(5);
|
||||
const sizes = ["sm", "default", "lg", "primary", "icon"];
|
||||
const sizeClasses = [
|
||||
"h-8 rounded-md px-3 text-xs",
|
||||
@@ -131,8 +131,8 @@ export const Sizes: Story = {
|
||||
"md:h-14 md:w-44 rounded-2xl h-10 w-28",
|
||||
"h-9 w-9",
|
||||
];
|
||||
buttons.forEach((button, index) => {
|
||||
expect(button).toHaveAttribute(
|
||||
buttons.forEach(async (button, index) => {
|
||||
await expect(button).toHaveAttribute(
|
||||
"class",
|
||||
expect.stringContaining(sizeClasses[index]),
|
||||
);
|
||||
@@ -212,7 +212,7 @@ export const LoadingState: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const button = canvas.getByRole("button", { name: /Loading.../i });
|
||||
expect(button).toBeDisabled();
|
||||
await expect(button).toBeDisabled();
|
||||
const spinner = button.querySelector("svg");
|
||||
await expect(spinner).toHaveClass("animate-spin");
|
||||
},
|
||||
|
||||
@@ -95,12 +95,34 @@ export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
|
||||
export type CredentialsType = "api_key" | "oauth2";
|
||||
|
||||
// --8<-- [start:BlockIOCredentialsSubSchema]
|
||||
export const PROVIDER_NAMES = {
|
||||
D_ID: "d_id",
|
||||
DISCORD: "discord",
|
||||
GITHUB: "github",
|
||||
GOOGLE: "google",
|
||||
GOOGLE_MAPS: "google_maps",
|
||||
IDEOGRAM: "ideogram",
|
||||
JINA: "jina",
|
||||
LLM: "llm",
|
||||
MEDIUM: "medium",
|
||||
NOTION: "notion",
|
||||
OPENAI: "openai",
|
||||
OPENWEATHERMAP: "openweathermap",
|
||||
PINECONE: "pinecone",
|
||||
REPLICATE: "replicate",
|
||||
REVID: "revid",
|
||||
UNREAL_SPEECH: "unreal_speech",
|
||||
} as const;
|
||||
// --8<-- [end:BlockIOCredentialsSubSchema]
|
||||
|
||||
export type CredentialsProviderName =
|
||||
(typeof PROVIDER_NAMES)[keyof typeof PROVIDER_NAMES];
|
||||
|
||||
export type BlockIOCredentialsSubSchema = BlockIOSubSchemaMeta & {
|
||||
credentials_provider: "github" | "google" | "notion" | "jina" | "pinecone";
|
||||
credentials_provider: CredentialsProviderName;
|
||||
credentials_scopes?: string[];
|
||||
credentials_types: Array<CredentialsType>;
|
||||
};
|
||||
// --8<-- [end:BlockIOCredentialsSubSchema]
|
||||
|
||||
export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "null";
|
||||
|
||||
@@ -136,12 +136,34 @@ export function exportAsJSONFile(obj: object, filename: string): void {
|
||||
}
|
||||
|
||||
export function setNestedProperty(obj: any, path: string, value: any) {
|
||||
const keys = path.split(/[\/.]/); // Split by / or .
|
||||
if (!obj || typeof obj !== "object") {
|
||||
throw new Error("Target must be a non-null object");
|
||||
}
|
||||
|
||||
if (!path || typeof path !== "string") {
|
||||
throw new Error("Path must be a non-empty string");
|
||||
}
|
||||
|
||||
const keys = path.split(/[\/.]/);
|
||||
|
||||
for (const key of keys) {
|
||||
if (
|
||||
!key ||
|
||||
key === "__proto__" ||
|
||||
key === "constructor" ||
|
||||
key === "prototype"
|
||||
) {
|
||||
throw new Error(`Invalid property name: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!current[key] || typeof current[key] !== "object") {
|
||||
if (!current.hasOwnProperty(key)) {
|
||||
current[key] = {};
|
||||
} else if (typeof current[key] !== "object" || current[key] === null) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
|
||||
106
autogpt_platform/frontend/src/tests/util.spec.ts
Normal file
106
autogpt_platform/frontend/src/tests/util.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { setNestedProperty } from "../lib/utils";
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
name: "simple property assignment",
|
||||
path: "name",
|
||||
value: "John",
|
||||
expected: { name: "John" },
|
||||
},
|
||||
{
|
||||
name: "nested property with dot notation",
|
||||
path: "user.settings.theme",
|
||||
value: "dark",
|
||||
expected: { user: { settings: { theme: "dark" } } },
|
||||
},
|
||||
{
|
||||
name: "nested property with slash notation",
|
||||
path: "user/settings/language",
|
||||
value: "en",
|
||||
expected: { user: { settings: { language: "en" } } },
|
||||
},
|
||||
{
|
||||
name: "mixed dot and slash notation",
|
||||
path: "user.settings/preferences.color",
|
||||
value: "blue",
|
||||
expected: { user: { settings: { preferences: { color: "blue" } } } },
|
||||
},
|
||||
{
|
||||
name: "overwrite primitive with object",
|
||||
path: "user.details",
|
||||
value: { age: 30 },
|
||||
expected: { user: { details: { age: 30 } } },
|
||||
},
|
||||
];
|
||||
|
||||
// Test Suite
|
||||
test.describe("Nested Property Setter Tests", () => {
|
||||
test.describe("Valid Usage Tests", () => {
|
||||
// Test secure implementation
|
||||
for (const { name, path, value, expected } of testCases) {
|
||||
test(name, () => {
|
||||
const obj = {};
|
||||
setNestedProperty(obj, path, value);
|
||||
expect(obj).toEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("Security Improvements", () => {
|
||||
test("should throw error for null object", () => {
|
||||
expect(() => {
|
||||
setNestedProperty(null, "test", "value");
|
||||
}).toThrow("Target must be a non-null object");
|
||||
});
|
||||
|
||||
test("should throw error for undefined object", () => {
|
||||
expect(() => {
|
||||
setNestedProperty(undefined, "test", "value");
|
||||
}).toThrow("Target must be a non-null object");
|
||||
});
|
||||
|
||||
test("should throw error for non-object target", () => {
|
||||
expect(() => {
|
||||
setNestedProperty("string", "test", "value");
|
||||
}).toThrow("Target must be a non-null object");
|
||||
});
|
||||
|
||||
test("should throw error for empty path", () => {
|
||||
expect(() => {
|
||||
setNestedProperty({}, "", "value");
|
||||
}).toThrow("Path must be a non-empty string");
|
||||
});
|
||||
|
||||
test("should throw error for __proto__ access", () => {
|
||||
expect(() => {
|
||||
setNestedProperty({}, "__proto__.malicious", "attack");
|
||||
}).toThrow("Invalid property name: __proto__");
|
||||
});
|
||||
|
||||
test("should throw error for constructor access", () => {
|
||||
expect(() => {
|
||||
setNestedProperty({}, "constructor.prototype.malicious", "attack");
|
||||
}).toThrow("Invalid property name: constructor");
|
||||
});
|
||||
|
||||
test("should throw error for prototype access", () => {
|
||||
expect(() => {
|
||||
setNestedProperty({}, "obj.prototype.malicious", "attack");
|
||||
}).toThrow("Invalid property name: prototype");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Prototype Pollution Vulnerability Demo", () => {
|
||||
test("secure implementation prevents prototype pollution", () => {
|
||||
const obj = {};
|
||||
expect(() => {
|
||||
setNestedProperty(obj, "__proto__.polluted", true);
|
||||
}).toThrow("Invalid property name: __proto__");
|
||||
|
||||
// Verify no pollution occurred
|
||||
// @ts-ignore
|
||||
expect({}.polluted).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,13 +58,7 @@ wwwDomain: "www.dev-builder.agpt.co"
|
||||
|
||||
env:
|
||||
APP_ENV: "dev"
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL: "https://dev-server.agpt.co/api"
|
||||
|
||||
secrets:
|
||||
GOOGLE_CLIENT_ID: ""
|
||||
GOOGLE_CLIENT_SECRET: ""
|
||||
NEXT_PUBLIC_SUPABASE_URL: "https://adfjtextkuilwuhzdjpf.supabase.co"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ""
|
||||
SENTRY_AUTH_TOKEN: ""
|
||||
NEXT_PUBLIC_AUTH_CALLBACK_URL: "https://dev-server.agpt.co/auth/callback"
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL: "wss://dev-ws-server.agpt.co/ws"
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL: "https://dev-market.agpt.co/api/v1/market"
|
||||
NEXT_PUBLIC_BEHAVE_AS: "CLOUD"
|
||||
@@ -77,13 +77,7 @@ volumeMounts:
|
||||
|
||||
env:
|
||||
APP_ENV: "prod"
|
||||
NEXT_PUBLIC_AUTH_CALLBACK_URL: "https://backend.agpt.co/auth/callback"
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL: "https://backend.agpt.co/api"
|
||||
NEXT_PUBLIC_AGPT_WS_SERVER_URL: "wss://ws-backend.agpt.co/ws"
|
||||
NEXT_PUBLIC_AGPT_MARKETPLACE_URL: "https://market.agpt.co/api/v1/market"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ""
|
||||
NEXT_PUBLIC_SUPABASE_URL: "https://bgwpwdsxblryihinutbx.supabase.co"
|
||||
|
||||
secrets:
|
||||
GOOGLE_CLIENT_ID: ""
|
||||
GOOGLE_CLIENT_SECRET: ""
|
||||
SENTRY_AUTH_TOKEN: ""
|
||||
NEXT_PUBLIC_BEHAVE_AS: "CLOUD"
|
||||
GOOGLE_CLIENT_SECRET: ""
|
||||
@@ -94,7 +94,7 @@ env:
|
||||
|
||||
|
||||
secrets:
|
||||
SUPABASE_JWT_SECRET: "AgBL6H6byPKXVN0nYWgqyoZBHJJJqk8S3rNYSu3JvwOsNn3Sw94SrJcXuE48fxcojcbKvYcDshetFhrSNEsfUwvbSFoaIj+MItPIZ8FGHyfXnmeZJ5j/sdvcjaMmWS7CiL2jhzI+nc8rL2ATV2TgA7E6FA9MvhWqkAyZu04pCd1c9DsAlpcfQ3pxywXjOV+BU0+1+++Z+fTnaugt+hRbhHrKTddaPhi72KrIyJPOUlqfht0JcgflT1f5frvmNnDwkiP862knhJsqg7XyrSy4msCN7eH4BvV01pO3KhAEnMG4Lk/5FeE/2t05+HqgB4mvPbkqfY6g8Lvf6qmd5g+stB10wRmr+TxzokOom36r7sYd3tyXZMgemJOi3BZjYk7774zf/TI081pJx1FPvM6dDPQdrgnU1nhshq/gLyKB5tTTCkPQHW+YhUtApWgrC5mq8ezMqfuwrUuR+NvyO59K56ERJJFAw1fDKHIVl4TYmftet42lkVOchml30cje9gjBtpOrTkFf8lMv5DLQ/ygdwsAYmVrpYtXbT+GAaRI9QIv/rZVuckfz4uIhTp7IjAtRXJreMH2V8GZ22h6Of/BQG+XA42lMA2huIyLwsqmtL0qxNmIu5EGjcXBvlOt5eNquzts68jgNqEfE6fEuqS9lDepvtTSxGp7wmcmWwOBbIgbCnxl8t4IW/e1qvOPuvxMldH+YtVIvfHMf3DnvbehhCIYuLmRM+E6139/qdGKreRyCi4PA4PKTMEG962rSu/7z0X5wu+UYY+kESrs7HhksL3PhB3dpbZ0HF+b6lchziTI0atG+UNX05ysL"
|
||||
DATABASE_URL: "AgACJkybaNon1X7ZkvtyM0mJ3Gfbjh1LWSJdWGC3ny6QU/8yERomTjvDylHuFoaoGeEs5ewYtgH87G/t1q3LF35cjmipGTSJbxFKfyoRcGBZSajen2Ijs+satVD7T6bmCNsurEsUD16QDLoV5lx+THXdEjv4VrBtFwY0HO2BIP15X/vMie9Mk91Uk1eze5dj8WoQ5OywH8O8Ugh7/iOleEyiPaMzxdRAfwvYHgX5QYqDno0ktZFyKnDOpCnegIwcUei4tt+EiTAG1SxESk48DQQokNrZc+lqlPwozX6FOYkgcQiZZqIN+9qOy5hB+1wKggS8zcd7/YAlSpSd8LMeAokflxOabN5Ctyvx3k5tGgstKQabW1TErwL8RjB3WYClLvJl8bfc4qILr4jpfA4all59f8oinATiyeqJ3Hx267FdHH2aywnXnNdEmycGhKkuH0vAa74oYsUiD9BrjkxmMdxCwmEjl47ob5fwcraKOc7hNF+hPi2C61/z1T6yoIeu4ediMTk0m1ZPIOotlbj/nKkmrKDfD+WMia/YvBpUtlM/SaEyX66gmO03kxW92cmXVCjK65oGh0ueCyK84J9e/2y+qO6yFXh7991+wTQyAedBBoc2myNcebKzuAmFNwanCs+FMOktPZsMMzfFy3oHJQxgFkPEp+jKennODqTe0A3LBhJC3ddwCYgd1TAABC2+DqFGaCRiyaSZ4BZIitEPFzpJwITIFZoRyxrCibrGKKnILVjGNaiV/KdIzfj70AAdzG/7GFdA9SKzRQimnVw99YTjNouCYzt7iLBV/8KrcMvyyeHZle1A6zg0gjjj4Yp6p0ssIOvhuDLjec4NMi/E5EbgYzKQFr7jN2u9"
|
||||
SUPABASE_JWT_SECRET: "AgCXWGuGOPbdUZsn1G+amfHxm1CZAmuiULAml7sDJ+SUXUBlBWOf/wb0Gf9vnjPrpS5QqeYUQDzVPIXRqs1BkL7m51zAYt4U5K1XGgF4VmUv5Jqm/99iLB/biuVBuGj32wuYzGNEfuPPz6sfg51AfPfuOCDE/bX9xD+M5E++wVQChulm0Lqab5GdBgZC2M90Cb57EjNbwkCnNhO1HY2etwMKJ5q4J6N8gW1nxdWemUFEtQuHRBwEhCA+XRVf4MHQPwixb0Kn7cA+D6OthxWaxsXgg0li/kY7tF4xed2XXkfhARFbATryExcnAXLOucWS1+NKjLSUCZdqeqR/+C6uKyAYQNj60WU9ikDG5LJrLgnB2dXVBDNFHWbVtWtHABdyIVENZlXCBYtIJUpDBVFVIZp/GOyR9mhwPil0qZOiLE6HXiRENyMcZxNk1E9BHQZHS6WKExsi7RP3kJVFcWknBvH8IydkoINRfGO+LNy+H8EK51ml60iq6pvNVRwcxfZBuNcdJQrWflsLbi2dT6u9tWM27COnV2UCWqJuslVhdEAdpeB/nErgnBcSWXl4vtApasxYw7l5QWTU2597TvMidqm9ardvFOiuj9SxRdLZ/H3x3rsn8yn3C22E8Ism4ciEA9wxjwL3pgWiSsRsIsTpI0++lHSSY7hZvsarwFWcgwzjZhufqjVTbRJiHGOeXijiAqj+b5haCkdYmJCFeJuph11GABtORqnVh2xkPfzB3iWpfshX+udtjr7vdJCcpQyRbq972I8sTqFJXQc5hXQYu8/JWApKnpYMFq7MtrCKGurYxLPx7pYaf1sA"
|
||||
DATABASE_URL: "AgB2qSmnSLX5mVx+kIhbD2Mb8mKFHSyTD/DlhaNLo00tl+O4ix155S8Ec9voSFwuDhsjuQC9vND9cNSDIMUakjKIu4SmKfyi1zq2dHkToq0N5jxOO04N9XbZtXRIUaw6/ME8NGodw0hF4kPMaKBcC3Vt4dvnZw4YOnk2l39W5Cf3GR2Jm5V2s4g90HLy+JexQjxf5YiPQdDUQPu2bRxJ+ELuaEH113QrbLJVQZtd18+0p+89pBP9Rw98YqO+Lbf8u2MPGXOYQpjpT+bIIQE3c42Wd7lpHd+S8YBfHOjJKipLK2L1JsN30nte+xHEC3Vk1re9T3Qa7Nmb4fuwnJXcXBxwyaOucsPS9o/beNGYZtvpR3e9TedcjxCe4DQpJiMmkhK3Z8eVYmxNUviccN21mf2yMsZ/9KNuULnlz+EFVW1vwmnuGRD4yHUK4HKmGyrKEdmnEOMv1W2yoRbyy9kjj3lTSO4jKDxAgs+9zkVUD1cIL/bFjJZVqOnXZs5I6gJWM5dGtLTqZe8IyxbKj9fORHLzl0kZqjfjiIVvJkLpV/okjaITVueuGljROt89ZDSVKCcHDiMMXF4qZwLNGIPoRT4O8b3BIBiRm2fnJu5ui3xbTfQWH1/OagWhgiBkvcKJ4IoZuxdlQnBjCOSig4BMUopjAQ49YaPX78qDBFTDSkk5ofogs1nKHRo41EmdR8Kl7JI="
|
||||
SENTRY_DSN: "AgB9i02k9BgaIXF0p9Qyyeo0PRa9bd3UiPBWQ3V4Jn19Vy5XAzKfYvqP8t+vafN2ffY+wCk1FlhYzdIuFjh3oRvdKvtwGEBZk6nLFiUrw/GSum0ueR2OzEy+AwGFXA9FstD0KCMJvyehSv9xRm9kqLOC4Xb/5lOWwTNF3AKqkEMEeKrOWx4OLXG6MLdR7OicY45BCE5WvcV2PizDaN5w3J72eUxFP0HjXit/aW/gK32IJME0RxeuQZ5TnPKTNrooYPR0eWXd2PgYshFjQ2ARy/OsvOrD10y8tQ3M5qx/HNWLC/r0lEu2np+9iUIAE1ufSwjmNSyi4V8usdZWq7xnf3vuKlSgmveqKkLbwQUWj1BpLNIjUvyY+1Rk1rxup/WCgaw+xOZd6sR/qTIjILv5GuzpU0AiwEm7sgl2pmpFXq6n6QjNOfZoPBTL73f4bpXNJ3EyMYDbPxOtGDz91B+bDtOsMr1DNWQslKkk3EIilm/l0+NuLKxf/e2HwM3sB15mkQqVZBdbiVOr7B27cR9xAnr296KE/BU6E9dp/fl+IgcaonMpTsE61pCLHWxQXNBO5X078/zhmaXBQyEBNQ5SPDr9u3pHWrrLkBtXwldZvgmLMMVFMAzrVVkJB4lC9sZj0pXPhda0/BsA4xcGRELj/PizwSr+kb3lDumNMqzEap5ZjEGCBpeeIVSo19v+RoEDw0AFmyxfYx2+91HsgiEqjEUg+J6yDmjAoRpOD1wRZOnnpR8ufMiqdBteCG8B5SXkhgto1WtDyOMVlX2wbmBFVetv2nAbMIA/l4E/Yv8HXiJsTqAkeYc5Qak6/SMGnZTw7Q=="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgCrHCd2AdIv++sX7AAf0YoV+qDFhPuErd/Q9Jgj8/1wDJklqv0giI/M7TRUV6j2Gqa/yLP90Hoiy2BboE0V3FrTtHzasxtSK0hd93+bxvZ34FEfKQyAiddBR8OxzlPwaplzaJ+/Tu+yHf1EesgXrUdydk38D4AQqkrC30FRKJcCxJNTHHzsZiHGQLZNP2l0cEsmqtMXMk8TqbcqHvJZRpr8jP1dSJ7bxEdU9mH/zB4HV+EsPLDFWFAnFjbEQwv8FEGgqpy81ifch4Hz7S0wjwk0x/QsagKavBTvI579K6Sx7uJyMyilpzm5Ct8kDXTEGUWv7pFINXM5cAbcBNzuvwvtXmshMwRsl9e/5Y2/T2VgS7/wPTJA4AmyyrSK976SOjo7imb4XfMwc6Cc/2GE0BRW9jiKvzjQ1TC2ovQpNujTYYgPzIq8sFXEVss31DIcfwbRAzgKTTQZKl+H+i9AS0q6iYHtKORwTQ2bv2XwQwxogXMHTUq1oC3MkzjKfV9DcoTHU2o/+gTyOBW5i3BRatuublA1x0EwDoEVmWA1+i1h2bpkl4QYuyeNlhJnRHzuQU3RdFLWn3MkDM8Q4Y9n0/XXwwTCgqtdAExqNh3YJYumWiGiWfdBpEUqlUtOUurNMXy6rHH4odnNKeLQfMOa9406x5H5xiwNkl3mzGjNiPDMS7JMTptlsoL8DshE2TM0PqZVrQy81OsGNpdiU8MVeUdHO6/bDBe7j9v0FipqpeehX1AZEYb/4CWosTJACWpaTnLYRh+w12bk3x6Sj0kriDKMOuJLBRh1fveZXUC9C0FsEPhq2rBLQDVh78DkBIeKVGUzuxDP/6mT3OSBPe4aCye0vTmwtEOEvB+A7rcMkOl+j90bKAveE9H+f7UVU6Og40Nc3sSuMolKHbQyB9TNd4+jOfmySSN675riL6BpFFCSuWqjrqWFr0yI1h/xAg+YMg8WzWarwSeWr3ykXrbhQvu7Oj27ffLXEIvS0gU="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgBKJDPEiTQUYLY0B/NKaAkxH7whrGuxQVtRdz9mEr/Bx06n1Yu1Zm4/oEQp3OvYerRvQWuv1k3//jf3eiya4ZW9+ZntfPdQWL9/tzq+/spevFtiEvuQ8uuUhtNOU4IGt27KTTlhCfeHKte8jtLQ/lwcrSrfPZ1T8Gy8PXdsAgakGUauEs2oHuX2XUaPE9UFF4HRAhmjPZ9e7u7Zfgcj8D+otjrwNVC5ZXFM7/ha0roeZHdpTvOcemKjxhiZA0FmdkXgOCPihNxlz0sKcupCEte6ocnSkpL3pBflBsa9+NLz7kLCUQPeOCExkMTndyqWk1kFci6j35cBP6PQlHfWPdo8OFCdG+3EfvEt5+4PQ08d7nXRZqowBiQdE2/e8qA/dZc8cJ7ecpza+9Qf/pNIl+9Ix0EFvmB6rpbO9w5Ptw2yMOAebVl7qV+A65GelvcPWROK5Vfftwx8KT2sn9ldVOYy+C7OafgOm8qaL7mEMePSwJy68MKpnMm/TceE7xxZ2sMSWEl9FMn4QXEawD4LQrJHKpum6XyUG2FlMkogHMikOEbJzz1ICAcHB6OWJXo3wU+fK8jkI4/UYioFSfF9MQXaC8bUGc4NT6T62KvjnrdkmOHG7HcN4UNQ7yBa/fP1pM7peYQdwAKr4Oxl4v3i96uKRdCuIimYiWpcxklkQhmSsMLKFMZGDDvv1BNtL6oxK4ZaEyWoorEyjkd2UrSpbP6cvyVMVWbTl1/BD350NRn6OYTpGIXwmAhqVWuuyt8kfLLU6Ot2MYcq/i16qvc3dA5XizLKHY95X8R+DlUCEzawF75Sx++eMPKU/o5rxhZNRjvffIcXtw1Hy/uqVCqilmSt33RqUsehOxQUqHBIW7L7sAik/L+hgTiE3MwN8XSfGncDB/bNweJh3ZSXsdwZD2bleEY1nWpsXVhcJDNcdW3YscJbsyTCGQcOb0zEQaxSmfLAd3rIa+SAZpFEOsD8A5kZjI4QpmqPkhNHeF8="
|
||||
@@ -93,7 +93,7 @@ env:
|
||||
BACKEND_CORS_ALLOW_ORIGINS: "https://platform.agpt.co"
|
||||
|
||||
secrets:
|
||||
DATABASE_URL: "AgCe47Y6QFnZRmNNpc3iVIcJ9zAZMPr/1OU61Slr0oM1yjneT7LgNTrAD9+QSu7Q0q2u+533FImOu8o/pKG0gNzEtyeSEEsegmCiaxs4Wi+/jVodU8CgfrYN3xkgi7QuD2fMs/zz+sv25LDg8LR1mIvkv+W/PkbqoVx3UAUXL7ZM+UvkFqie+69C/kwoH51xPaYi+FajSzql+VdhDc28aFkwrDApenyLbyKAHSnplWqpFDVtcQK4dL7K9EhRkUnWORNHiYT+N0Ras5tw1ief481nDud29nhlQ+5HIHGK5QouKmA1yJIJqavbzsDf7n+nWCwULIWXGi5d8A23e8vZAuF2CInUHAmP8d+xBDRx8aATZigoOM0GpHn5ljxNs0TCZ1HvQ8TjzQOpcgmBZ9l5Eod3y/PRhjGBRrcR550PAxpkjRj9VjfMN5NSiHVsGw7xKQr2+4yghv3VgSZFvOg1qUxmleJ9khwZq2Gel0uvhGStYw50x/C+EDMaJNQUaBDDbo5Bjy6/RNAEgrX0snQAZWCZSg4bsb4+2jRvo50W1wMr9076qOFViNn2YM+exIGXdmgggAKk7foZDZz1KgG+dAhKBXoUhXwHO9P450gq4xxDGSk0ZCe10OiszNkXVtmmE+Ryi5JjnbDsxLJP1tynrljFUb29VePt0zwZAhdnz0MQU1T9IVbjcKYB92h19lhcdgE="
|
||||
SUPABASE_JWT_SECRET: "AgDCNoyGjkV74NoJTSN+VDpiIl8B7sRzDK3WFAO2wBWdhFSwnO2x4cZ7F9YxiEQgvjslCEn5wCrgqBiO6L3EP4qd0EUibZrx+6xXIK5tRY3YACXf1pQEgRTl5fXTbdlRJgr0+2upe1bB30LecbM/G+goX+RprKmwSxNJfBo/AQQP1l1dJsl4v/yGbEj8WQFD7jTiyCGT8skIX07GUvzZVbAD+DaNmvucuENja90O7q3ACOIJ9VWX9rTJX1V5brrb7y4ge8Iv97bUisH/MtFtZMEBEK1csixdBUC47wtboxn3LnMUaE2OlJZD+9IzEf6TqTJGREyOd+h2V/8BmUYnKRZUVWPURsWLinfbDpyXPmGSBgnfYCd5ZKS5dvwvQwiR6ODlO9E2WOe4xoAHSq57uUxLopcrDsVzZcLiahA02613vYafwc9svg/8dJV2FokKrnQs/bRHlVCa51Vs8z/zCBnHzS6RBq2p2vMU/PbzMfMR2DjzQcvE6hOnyLMlOMpYe67FaHC2cZTayUqNzi5hDNUp19grEqwjdvannOenn/ewTX4yCOVaOoAQ+QBrv1o4g46E18wFnDMoYH+b7PJ0mUf3NSV0jXH+elbW5H6c5WdnmKT+nWQhaVTD4pMAlY8z2W6LddpA7XHfj/lAzboSj+VJqwCrx8AVMKJ4LTbYGTdu9RkcNZLVHkrtafEAYZNnYrIwNZT3SuLWO8X/dGKrrZmOoaaQJZkBuRy1bLdOGXKUIBvf+MDvmApjp/+ScDxl11KifiEO3tO65hNVa2YPjkIf36fJpJ6vtDchsmyowJ9cRhduZsu4PO4D"
|
||||
DATABASE_URL: "AgCUKDIUv1z6KWRBX+sSfokUYFPZPMurFMcvVG4GKHPtV9ckIZ7tV2S+FxdQcXNnUQeuqQ7lSpaxOp6fnzC2ou9A+mHHstiEl0u9QrrL1gyOF3AsEnEd+TpXTZ3gOq4v6vmEqErNShrm1AiZy5qKQi50JoUUZNh9brM2MmtlSZU58BWxLTjMaEHttPsdQWz3mlumJchRxJzr9xnzVZWwp3Llq2hTGOdwa2UNljd9h11m547/fT2d7o3vqP/Sju+h6SEUB83EHP2Ncjxf9rhVn5KXboxvOSbKXxa8eu5eE87D1tlrCCvnoDV5hh8h+W4EboFwUffJZDD9+VPK5nIJuCZ5k9y/+9peQfOCvQ8LJaNxKxzBfGXxsb9PmYJrdN3KAbE5ZmhjuABvuMcPMdJRxGdwUCNCyw5XpiEQRD0vSSEXuZKOv3Oy0a6mOzcNf2a5QhAqAM98WazaCjg4gmVQ4ZKDUYzjsURpGavb/piBo5pDZHrwWjCilsjJqTFlPTFi39ppVtXitfncYCUPYVts/kNrWa9//4sj9ZKIKUsLzCB8X1B6pA7cG/9u0n61Kllmp7naWM2W9Jq0b7DB8FvihcyMsOPxfyeX66OzTFypat3+cY4JcLr4rx6DPk4hZyHfFnJZdfeSl/MePPkjLTS1aBLZ+sCNaPGWKaEJ97zZSEvm54V2AH5EuLJqSx3EUotpLok="
|
||||
SUPABASE_JWT_SECRET: "AgAYMdZyP+UhxIdTx6qyRzq9xf1dT7S+DFEC8KSPEFydX9+hAdJVTpprOlgLnqSbfSDmbqcFnCH+aK/6rdRx3HI3v41FogyCNFFxTrfxq1Esk8VuaVh8XrO2xKPd4iGBPZaTrenKlgt89aGdjPJzgl+NlZ5+/BXd95P2uX39DDGr9GJdO14zBt69O+L+Yt7kdd3ZMBjWYibZAzf+YaNIx/M7jjzGLYvxtywMVTrR+6e6GkGQSt5CzBpgk1b6ugPVtFs7PqmMtUqXMQjlrW2u7WVZRWeXO93ukc/TtjO2XUY9JfrgibMf0H81NDDTAAQBNqaDk0LdXsPUo9QGnyeQZTsfAOaeM6lTxX9qCYjneN6pxe60U1BKLURpordRdBs3peAedNJ95GC75qcdSkZE2agjwJvXKs8yy2Ig5eiU/80W27IWPMSLWhMSSf4ixyfkNWM4EfWL45bXlVGvtYaeyqByb0QU1g+II3AukIyO1qOS572y0sGseEv/UlfU2NDBLFejeBZaz4s/20lSyLhP3v1Y9aTs8qWIGl67syFKZoCwPRxwip2v7wIDnlDYXtlxMpQUWDnSUX16zQiVALD3izeDYkd1RViBgdYT/G0tp6lBeV1vnF8tBEGWIl3GJFV0okUflAQ9NIrdC5+BlcQDD08Jn0oGjyje7KE/BfvB1lHT7K+h9rr8B/U8zBSaAe+KFjA8pcjHqXgi4Zx3ayTXdAddyFZd0YqONohEAvXB+BLLdYJVNNXjBFwY62XQ6ojD2ZYWz4m/Wo+/zG0Zm5s/v2VS8UT5qe2Wjs3oGHKIJc6Eo3hVwLefcb7V"
|
||||
SENTRY_DSN: "AgA+X2HkF9b3+p13JS4vG7VY+8p7su6qJ7smoPKqYh44Vpb7J5Eu9ksPNQkcFTDDPT8jAylqsHWUdI0A8u20+a4lqqGkmPN5tCgyBgAL1pIyvPUQjYUbL7A5lTQKlRLJJ+05h5XbkRU7cWR+G4yDUCDj2HcThne0CNDUbDao9D67ekSLUtp6/d0KO45Efao4MLuqISnypPUBGHmAdWGr2z/w7ItXjvUKt3RpH6pSCrGzjlKPKhenKdTsk/NX4Z+ew/JBbHiDQjKCdj0UlXFWH7Q4axaFy0T8tsqf/UN7n/QTalYE+v28isxrHvoR6h7kZETQV/gl0y7DdmTCi8/A1j1+e/9zUx6HvK+C/qGMsKMdNgaaVNSdfFp/yfMgXTUn4HGAls4gjVKSSRaIAbBq32NdKkIvRfocuAGsxInwbrDXLR0nzbHG/U/QhlvfL2gfqKRIVRJtEh99VW/KMMeXZUWR9dNt9gfTMtyzL7eta4oEV+g7sdO/9VjDn5wtic2/7eAxgA7wTEoDA8m0whpHH4VcPLHUfKLTHnRXVu6bykAfBgfEKhJBS8DghvPyu73qL5MREuYkGya4n0RQ73h5ja7mYwI0lsefQszP9Fz1lR+757dhJ6+/E7nNnOE/ShD/8xE0V54pd2IvrRoJmcOsIOZ5w+xWfmN8OyLn7wuEpqEuMHEoisLF9RSp2V5iKbB+fFB4o5P1/VqkNPEFBe0jA4K8DAGX+VdChMpjAI47wF22aj+jmTRf+EY+5l+aEvjyU0G7oUPVzzG8rYa6p+v56zeVsmU4SHIDO75J1cH7tnYDeOxk9fAYZgNplS4gKHVT0w=="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgC07IPlKFO0JYHzO7/H/SVmws9x4mKUd08OQ8VrrvTrwXTmmkKJ0nrbCR1tqSEQTGAFsoTbAtJjVmz04xDsBRbuBuTfbROcD057J+4nKSEvke0rUPnBESiziYQF1Xo0xC80li6OEpiz2ssp5yCF0oddqOkXGR5BfKJ8ZnOS+YkvNla4uE8fbzMUGJU0Erw+/DUOVdIIbF8/P/k55VXbhzPiafMuISc4+vH33/SLeSnUctUiVq43x42buYApt1qb/0cOKElRWtdI1YIx79mbhG9RbE3XinYQ11Fbvo/SeJmTI5YtatH8aesFKSHJy1svzULPEG1Fx6B1wvlROxtiu4Y3WV2YEHOLMXUbrM1CGsqO/W07ohyAGAF/KlhHfYCl/ZqAs+BtuBvPtXXPkxSEj0clqknAW8x8wdJBckPn3OVba0hsmBPcw4NNJPYf9X95jiYMMP+TrrpryTygL0TBcH4agRCu1AXukzdjelvQhRidzV6ZoRxP3e6QjMcqoFW3jx9TAKjj5iZcC9BIIiic3XYCWVv5ei2HuFG2jDBbVDNaNFIOrx/CyrqNIdNiyPX0DH+GilUY4AoZgN/k9syvmWi2sl81yLOt27gGDjGSJTX2whNztcJTDWLgl7t1snj9if+3KzROR/Kmz1XawaBGK2Md+JyiugludHmC6eOO+TKXrV/d02LEZKf0aKnAuAu+gfBGBBagHF5jjkgmCYOGQpALDZWsafqmUsHOIvHmrAEpnKwRCW1JX7Z1pU6SkFm8ceNwsEFPDqIeYbra3k/pHxoSMA5QAO/IafFQdp6NTnYPduJN5yjRh6MSJPIf2U2PFgLnNAug/3Uojk6I6NikcORZtBCHBGd9dtYH3ykQdBSh44ysS3m2o6QVqpbQRYAaUbKEU/wExtZYWK9NzlbuxmRoSKJDAXtjG7Lzgx66p+fZAxYU+MPgxzaoyM53kfqGtLfuikAxswIyvK1+mcq866tyqLFSOwHoQKfez6Q="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgCADpjXfdpTDruyK2F4GRNT/Kl+yaI87mQdXDHQo3jOC3gWoOiRlXg70JG3jIi2cWjAXwU8ySjpT87aJdRwsMToeMD78zr0FbOSB2abx7OPTij8zWFSzhIo4cLoEkvLxZO9HXwQc959Cxh5oBcn6WBhJ5XMUxNWALIem9+Lb5Eu1CwxSF0EDrl3znx3Iqw/zUqnAgS+Ob4AAiJwXNO641ja7dAKYkb2NJ/KCBgmSXAaPfxQByuNkGP4iwmQuxhhJQ/N+LRVCu03J6NLPVw22feKKtZxAAroMDn5wPhRdmzBawqbRsejiCb0JNL2yd574CDN5xzsDur/RYkCpTrMWzgnN3F1VcYMuB9FwYazKU3XqviOYtP8Ca4sUQChHQEOFP8n3Nt0Z17zo1NtgRt8IBpXpDeZFgDZU6Zy8EtpHHn05KT8YqyLDms2LfJhduiuyndbZgeIfr7IcxbU4aBafh+J/tfN7Tlj5NFYxFImKQ0NFg5z6W9zKKkfFMo9WUcOOXgwg8+g6xeZUX9g3rNpMBNf2bt0UfNqSIBeAmUZVKHuEqneFONbgtqOP2NKsKsSfvCsnpKgAndv+eL627qWAuDzywWuoAcxsF/Kvo/fQnv1a+7abCr1Qhf61u3DBriGp6TAVhQ9z7iGqvkuviELt97NKekeevCgdjwWpk78iKBCmxJobBTErdX2Xhrqfc5AHteoUBYv3TS7N8ZcOmfVmZc2ulgLLQZZ5hK30w9FFu28bu01ArfKcSp3U21keaC/cGHBNdUWgAbg3wIH+3y3vU2MRHI6T6sFrRsNgJH6b8S+HcOInTsoaLFiRv7SYxGYliV47AEukv4G2G+9XO4i4y9P90u5i7KM+J5FRlR6sfiISPozGHUBe9EAKYQcqaSSGP7FWsyNl6DGq/pDkG8IJYqNr21Sl9N1cdhK/Hdd4J80q05A9f3AyzHjtU4YVcvz4TCKr1FJLugBUsz120cA8FxGXweIQRWCzGvSeGA="
|
||||
@@ -118,8 +118,9 @@ env:
|
||||
secrets:
|
||||
ANTHROPIC_API_KEY: "AgBllA6KzTdyLs6Tc+HrwIeSjdsPQxdU/4qpqT64H4K3nTehS6kpCW1qtH6eBChs1v+m857sUgsrB9u8+P0aAa3DcgZ/gNg+G1GX6vAY2NJvP/2Q+Hiwi1cAn+R3ChHejG9P2C33hTa6+V9cpUI9xUWOwWLOIQZpLvAc7ltsi0ZJ06qFO0Zhj+H9K768h7U3XaivwywX7PT7BnUTiT6AQkAwD2misBkeSQZdsllOD0th3b2245yieqal9osZHlSlslI9c6EMpH0n+szSND7goyjgsik0Tb0xJU6kGggdcw9hl4x91rYDYNPs0hFES9HUxzfiAid6Y2rDUVBXoNg7K7pMR6/foIkl+gCg/1lqOS0FRlUVyAQGJEx6XphyX/SftgLaI7obaVnzjErrpLWY1ZRiD8VVZD40exf8FddGOXwPvxYHrrrPotlTDLONZMn4Fl46tJCTsoQfHCjco+sz7/nLMMnHx+l1D0eKBuGPVsKTtbWozhLCNuWEgcWb4kxJK5sd1g/GylD43g8hFW531Vbpk1J1rpf7Hurd/aTUjwSXmdxB2qXTT4HRG+Us6PnhMIuf/yxilTs4WNShY0zHhYgnQFSM3oCTL6XXG1dqdOwY2k6+k2wCQtpK45boVN5PpBrQuDuFdWb/jM5jH6L8ns0dMMlY3lHM459u7FEn8rum/xXdP/JvpFb+yct3Rgc54SOT5HuVUNAHzzmbWhY4RG4b3i21L2SlsVUwjKvu+PlN4MN5KPilvHe3yODXZu0Gp0ClzDNZQiKQU67H0uYr6eRccMDsHtMlPELqnjyQZ+OriydzB3qXidAkguKNmzPypz0LyTMnry7YpNRGyUw="
|
||||
OPENAI_API_KEY: "AgBzA0t5U4eXqjAIZ1z2zKWGSYKuPuWMe38gg9DyU5ELbvpWbkBuknl4B1+2PH9Cy1Ma0NGfRCW5p5iqX94ay5y16lWycExgw//ue67E0x5YxZw7Bmp65g5a8IGl1y2uSEEJU7DGGKOSGU7a0vjtLHqy9GfhuGMMF8bKy2Mfz1Q+46f9rtwTs25mk+8tGvL0HJqjgX38Xuu8lbZrab8ywiO66cIJo2B5ncx+kkQ46ApLwWgpsjK/AEbvoWdlCws2PM0Tn3CLV2vDkFSPLyoBOfNfHRcCJuVi8nhzPB2W4XnRqdMYy5/HhRqbU5Fcp4eFSqiMwtd8KdpymijrawcdTgQJX8luv7F5DOVukP1WAMT4a8s7vumwtEeFruobBH8ztrHpGD9rIFJQKCsZwzWEBIRicU2yQlb7L8EhUkCqkUa3yNui7nS4sxRnQx24QHMCdUAdgNB0PiU51RDVbY+r2Rbpj1i6htmp5a64bd+n3jow/D2DYxlMXVEpcfLnhPhYfpsZegCoIaBcOMyCg+p1PUKh9w5TUbByLHAXiwdMk+vS3qf49fCOwvsqq0gBeCeY+7lBPYc3iDR+S0V6rnVv3PzM3fD5ighLGm7+z5eHrKxQapflELAmm+t6usz9wHMRoAzQdu1iuYeaoq07fu/yN2pdIpZ4bYB1vFvpqGq1E/XLFaoVy9tT63vLjwL/URxhk4kUYejCXST3gemm6o2BPoj2tU00SKzttLuJbcaYsyGRyiH5xhcwcVIIMsau+gqG1ofMVp8YJBxIKD0JNyg+/bE+iORIjZeAoRexUfhZfiEyjmPj5bHJgtkMoiWrLnBSIbhjDrW5narzxXgWyjoelSH8x1ko0kVGtHHDCdWoTIHjMA16HiLqv3BqzeB5cXNeqY1afbZe8KH1rUlSJB4Tg2qz6+AEUQ=="
|
||||
SUPABASE_JWT_SECRET: "AgBL6H6byPKXVN0nYWgqyoZBHJJJqk8S3rNYSu3JvwOsNn3Sw94SrJcXuE48fxcojcbKvYcDshetFhrSNEsfUwvbSFoaIj+MItPIZ8FGHyfXnmeZJ5j/sdvcjaMmWS7CiL2jhzI+nc8rL2ATV2TgA7E6FA9MvhWqkAyZu04pCd1c9DsAlpcfQ3pxywXjOV+BU0+1+++Z+fTnaugt+hRbhHrKTddaPhi72KrIyJPOUlqfht0JcgflT1f5frvmNnDwkiP862knhJsqg7XyrSy4msCN7eH4BvV01pO3KhAEnMG4Lk/5FeE/2t05+HqgB4mvPbkqfY6g8Lvf6qmd5g+stB10wRmr+TxzokOom36r7sYd3tyXZMgemJOi3BZjYk7774zf/TI081pJx1FPvM6dDPQdrgnU1nhshq/gLyKB5tTTCkPQHW+YhUtApWgrC5mq8ezMqfuwrUuR+NvyO59K56ERJJFAw1fDKHIVl4TYmftet42lkVOchml30cje9gjBtpOrTkFf8lMv5DLQ/ygdwsAYmVrpYtXbT+GAaRI9QIv/rZVuckfz4uIhTp7IjAtRXJreMH2V8GZ22h6Of/BQG+XA42lMA2huIyLwsqmtL0qxNmIu5EGjcXBvlOt5eNquzts68jgNqEfE6fEuqS9lDepvtTSxGp7wmcmWwOBbIgbCnxl8t4IW/e1qvOPuvxMldH+YtVIvfHMf3DnvbehhCIYuLmRM+E6139/qdGKreRyCi4PA4PKTMEG962rSu/7z0X5wu+UYY+kESrs7HhksL3PhB3dpbZ0HF+b6lchziTI0atG+UNX05ysL"
|
||||
SUPABASE_JWT_SECRET: "AgCXWGuGOPbdUZsn1G+amfHxm1CZAmuiULAml7sDJ+SUXUBlBWOf/wb0Gf9vnjPrpS5QqeYUQDzVPIXRqs1BkL7m51zAYt4U5K1XGgF4VmUv5Jqm/99iLB/biuVBuGj32wuYzGNEfuPPz6sfg51AfPfuOCDE/bX9xD+M5E++wVQChulm0Lqab5GdBgZC2M90Cb57EjNbwkCnNhO1HY2etwMKJ5q4J6N8gW1nxdWemUFEtQuHRBwEhCA+XRVf4MHQPwixb0Kn7cA+D6OthxWaxsXgg0li/kY7tF4xed2XXkfhARFbATryExcnAXLOucWS1+NKjLSUCZdqeqR/+C6uKyAYQNj60WU9ikDG5LJrLgnB2dXVBDNFHWbVtWtHABdyIVENZlXCBYtIJUpDBVFVIZp/GOyR9mhwPil0qZOiLE6HXiRENyMcZxNk1E9BHQZHS6WKExsi7RP3kJVFcWknBvH8IydkoINRfGO+LNy+H8EK51ml60iq6pvNVRwcxfZBuNcdJQrWflsLbi2dT6u9tWM27COnV2UCWqJuslVhdEAdpeB/nErgnBcSWXl4vtApasxYw7l5QWTU2597TvMidqm9ardvFOiuj9SxRdLZ/H3x3rsn8yn3C22E8Ism4ciEA9wxjwL3pgWiSsRsIsTpI0++lHSSY7hZvsarwFWcgwzjZhufqjVTbRJiHGOeXijiAqj+b5haCkdYmJCFeJuph11GABtORqnVh2xkPfzB3iWpfshX+udtjr7vdJCcpQyRbq972I8sTqFJXQc5hXQYu8/JWApKnpYMFq7MtrCKGurYxLPx7pYaf1sA"
|
||||
REDIS_PASSWORD: "AgBKMJoMuj4Aze7QZFm0mmR+7FJ/1Shc/fvFMc1yv1WcyT12ngDlSdmw6eW6PaAxnrzTRZbjGNxDVONS/8g86OvEEe+OiZjI7iaGxipGkxeKMzHPbHgQt97gKRT0wEQ8K6d67gD72YZDpVmYKMOWlMDIWl64404O1Xq4FJeBQQiB57MpP5VBX0Haxe+piYfyCcli/V9mZqLb8rzutl+IovCzd3z+rpJ2EC9kgCWjGzH0Kaylmrg86ZFFSQScTcv+UQ6/7y2WldVJPohMFEOFbxUXEThzkPxy7rryNNDrQ2M704a+/ixAqhQ9nJmaAfMNdFgp4T0oEQlsTPBEsXwCt3yzqbdAm+eAohe2X60d+trNsHdMGEzgWDFtTLEjCdKml9a7GJMJsZsf2Qb1AnvdwlLFWm9jm8X+x9YXrHvakso+zvRCB1uvVEB+77ys4y0flBXDheFOTsS7cnGfumexGV/0IrJPBujVJM1q6J1ilPGTYqWVpSznl4taCPvkGjFtsKj1JHlc1FMkyV9vmkHfMfC/YuYYzMpKcfMQlUh22gpth69ENhN3DNUUEH3m5Ea4hzG5lsiCJ9XFJyJ4RSqUU3U58zy18ONEzlX1qNb26oqTSe2j5+29JpTAOkmcRyMBH0WHhB1Us5vgYjN0WNKY4EKLO53kxJDJIKiquEb1mWAmy9yzft+LhroqpyhAUtTvh5MLVs1CCpUX2Q=="
|
||||
DATABASE_URL: "AgAWMrGQg5ON4uR4ZBWZWvt47nuT57L+D63i9vnfSMH9IGAthi+Z056Om4sBfyjpIOT4uowECnznRWQOZLXMrNeIk3Hwc2cBqFuO2LpnN4UnaRQHYNzp2gn8onwq54XhOhQF/aw0sSMBcXWJKfBOY44wZI0vebFaOE1h615tXVvTz9g8OFavj2Wky25ydHZiukpUs7M9eu7FE5RSGiG8saHL8+VLQRWqODf4dcykFiFcp0hNbaNSmYpAWyI8oe44SkzVb2AWbEZEUjkNLVxV95iMeTi0SRNaJ/hW6wcN7O2ZByb2jwRKiXXRa1l9b5Uy9hcueY1VU5ZCDuX5XNtM9BnfbO+Ez0FCc9J8DhfSi6woG4WceWksZ7unEUspPFKVRTFlKqn5MPR/ppfNwatlCpgf9q+zDybrMRoiD8aOcn7QSLNcs+dRRmFNDtu2+2RMBhoYSuENyOxZZRfjg+xYOL29BGx0SOQ46Jk7W/ZC706+T91SHfvTFMqStWFrYQOBxUcoDSK3XidJ/CYcXw3xzXpM4DTSbKVTwKVkPy72DeQf+eTxk0vS0xnbz3SHZVBMrRAcCfvS1HwZGc9TuV6dMfH6i4WKpPt2PxyuDL/sqt0RdHCKjPL8dC+XozqPTv0tTMQ9Xof2bllXWCN5QDWuC4Gam9M8aWKvALqtxwjDJmeqfqImPe045gsIUEX3pGYvB4PHfRPaocPe9sutjfa5EVk1md0m2yBpBcxouJBwV72sqK3yMVFJ+UYu+1nasiNkG828/hFYhHxlV+oUrGHpDV+p2c4faFUMEyo6FC+fBvM1h8JgM+Bk8kaf8YSUuop6Xwc4s/yF46dB6CdCaVJPvI9m3elj6Cg9EAXq7GY="
|
||||
DATABASE_URL: "AgBiuPoCatLyHm2T4JojAjXxjd59gDazf2eSPGFjtagTe/ue6crSW9oios4+kzDhSoK+t6CVqBKtRZRK2pzeKJ0xNsEQbCPU8xGrymUS2HsBuadSKz6opJzsrF7V8cFsiZWl8aoJqV1QR2pbRf/o6ws/g2PiXnbykDPwViamQlN+iZXzYA35h8QPYgXLkdWXzqII6cnpxdkgDQGFuZxkKTm6yqX4tKwCT5GcpNNV333IxX33ljZQDwBJENxAGs43wH8KOhSeVq5uGArJz04teagn6zAxVhP6ZnoK77FCCCHzgQ5eupigBxWnLXYSuC0652hcmCWnVTy+eJzGAWvCwdTk45xZ2fyvxj6uTc5DG9Pqk1U5SlLr9C9yvou9Qwd30M4q/Sj9t4WtH4wMIuCHRp5uaTzDHdW+XHIhflRIPJD4XTXvotsbawCgpulwowrtWXtiDZmUJ1IOw12tXnBROk62lglfeb4y0zCc1snpBQeAJd1GWrksZ/j+VRTl6wJFCPfnQot1g6qccBah4Uiz266o2aybcbZH6nIu/hCrRX8QSFrZQZODJoLGZH1XDjYEPX/LHVaCRsQiBiFuWZbYqcU4RhmOiM/KKTimBsl0lzlAMEz8ITL0sLJnjJleqdqPuDp0IAkAZCjHK9cshJIv5Kxp+m9TFRSSscCRSFeVCqROaxZsYKpfdb0JaHFWaZ/h8Is="
|
||||
SENTRY_DSN: "AgB9i02k9BgaIXF0p9Qyyeo0PRa9bd3UiPBWQ3V4Jn19Vy5XAzKfYvqP8t+vafN2ffY+wCk1FlhYzdIuFjh3oRvdKvtwGEBZk6nLFiUrw/GSum0ueR2OzEy+AwGFXA9FstD0KCMJvyehSv9xRm9kqLOC4Xb/5lOWwTNF3AKqkEMEeKrOWx4OLXG6MLdR7OicY45BCE5WvcV2PizDaN5w3J72eUxFP0HjXit/aW/gK32IJME0RxeuQZ5TnPKTNrooYPR0eWXd2PgYshFjQ2ARy/OsvOrD10y8tQ3M5qx/HNWLC/r0lEu2np+9iUIAE1ufSwjmNSyi4V8usdZWq7xnf3vuKlSgmveqKkLbwQUWj1BpLNIjUvyY+1Rk1rxup/WCgaw+xOZd6sR/qTIjILv5GuzpU0AiwEm7sgl2pmpFXq6n6QjNOfZoPBTL73f4bpXNJ3EyMYDbPxOtGDz91B+bDtOsMr1DNWQslKkk3EIilm/l0+NuLKxf/e2HwM3sB15mkQqVZBdbiVOr7B27cR9xAnr296KE/BU6E9dp/fl+IgcaonMpTsE61pCLHWxQXNBO5X078/zhmaXBQyEBNQ5SPDr9u3pHWrrLkBtXwldZvgmLMMVFMAzrVVkJB4lC9sZj0pXPhda0/BsA4xcGRELj/PizwSr+kb3lDumNMqzEap5ZjEGCBpeeIVSo19v+RoEDw0AFmyxfYx2+91HsgiEqjEUg+J6yDmjAoRpOD1wRZOnnpR8ufMiqdBteCG8B5SXkhgto1WtDyOMVlX2wbmBFVetv2nAbMIA/l4E/Yv8HXiJsTqAkeYc5Qak6/SMGnZTw7Q=="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgCrHCd2AdIv++sX7AAf0YoV+qDFhPuErd/Q9Jgj8/1wDJklqv0giI/M7TRUV6j2Gqa/yLP90Hoiy2BboE0V3FrTtHzasxtSK0hd93+bxvZ34FEfKQyAiddBR8OxzlPwaplzaJ+/Tu+yHf1EesgXrUdydk38D4AQqkrC30FRKJcCxJNTHHzsZiHGQLZNP2l0cEsmqtMXMk8TqbcqHvJZRpr8jP1dSJ7bxEdU9mH/zB4HV+EsPLDFWFAnFjbEQwv8FEGgqpy81ifch4Hz7S0wjwk0x/QsagKavBTvI579K6Sx7uJyMyilpzm5Ct8kDXTEGUWv7pFINXM5cAbcBNzuvwvtXmshMwRsl9e/5Y2/T2VgS7/wPTJA4AmyyrSK976SOjo7imb4XfMwc6Cc/2GE0BRW9jiKvzjQ1TC2ovQpNujTYYgPzIq8sFXEVss31DIcfwbRAzgKTTQZKl+H+i9AS0q6iYHtKORwTQ2bv2XwQwxogXMHTUq1oC3MkzjKfV9DcoTHU2o/+gTyOBW5i3BRatuublA1x0EwDoEVmWA1+i1h2bpkl4QYuyeNlhJnRHzuQU3RdFLWn3MkDM8Q4Y9n0/XXwwTCgqtdAExqNh3YJYumWiGiWfdBpEUqlUtOUurNMXy6rHH4odnNKeLQfMOa9406x5H5xiwNkl3mzGjNiPDMS7JMTptlsoL8DshE2TM0PqZVrQy81OsGNpdiU8MVeUdHO6/bDBe7j9v0FipqpeehX1AZEYb/4CWosTJACWpaTnLYRh+w12bk3x6Sj0kriDKMOuJLBRh1fveZXUC9C0FsEPhq2rBLQDVh78DkBIeKVGUzuxDP/6mT3OSBPe4aCye0vTmwtEOEvB+A7rcMkOl+j90bKAveE9H+f7UVU6Og40Nc3sSuMolKHbQyB9TNd4+jOfmySSN675riL6BpFFCSuWqjrqWFr0yI1h/xAg+YMg8WzWarwSeWr3ykXrbhQvu7Oj27ffLXEIvS0gU="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgBKJDPEiTQUYLY0B/NKaAkxH7whrGuxQVtRdz9mEr/Bx06n1Yu1Zm4/oEQp3OvYerRvQWuv1k3//jf3eiya4ZW9+ZntfPdQWL9/tzq+/spevFtiEvuQ8uuUhtNOU4IGt27KTTlhCfeHKte8jtLQ/lwcrSrfPZ1T8Gy8PXdsAgakGUauEs2oHuX2XUaPE9UFF4HRAhmjPZ9e7u7Zfgcj8D+otjrwNVC5ZXFM7/ha0roeZHdpTvOcemKjxhiZA0FmdkXgOCPihNxlz0sKcupCEte6ocnSkpL3pBflBsa9+NLz7kLCUQPeOCExkMTndyqWk1kFci6j35cBP6PQlHfWPdo8OFCdG+3EfvEt5+4PQ08d7nXRZqowBiQdE2/e8qA/dZc8cJ7ecpza+9Qf/pNIl+9Ix0EFvmB6rpbO9w5Ptw2yMOAebVl7qV+A65GelvcPWROK5Vfftwx8KT2sn9ldVOYy+C7OafgOm8qaL7mEMePSwJy68MKpnMm/TceE7xxZ2sMSWEl9FMn4QXEawD4LQrJHKpum6XyUG2FlMkogHMikOEbJzz1ICAcHB6OWJXo3wU+fK8jkI4/UYioFSfF9MQXaC8bUGc4NT6T62KvjnrdkmOHG7HcN4UNQ7yBa/fP1pM7peYQdwAKr4Oxl4v3i96uKRdCuIimYiWpcxklkQhmSsMLKFMZGDDvv1BNtL6oxK4ZaEyWoorEyjkd2UrSpbP6cvyVMVWbTl1/BD350NRn6OYTpGIXwmAhqVWuuyt8kfLLU6Ot2MYcq/i16qvc3dA5XizLKHY95X8R+DlUCEzawF75Sx++eMPKU/o5rxhZNRjvffIcXtw1Hy/uqVCqilmSt33RqUsehOxQUqHBIW7L7sAik/L+hgTiE3MwN8XSfGncDB/bNweJh3ZSXsdwZD2bleEY1nWpsXVhcJDNcdW3YscJbsyTCGQcOb0zEQaxSmfLAd3rIa+SAZpFEOsD8A5kZjI4QpmqPkhNHeF8="
|
||||
ENCRYPTION_KEY: "AgAlH+nrWFAwm3DxUjlKTdjNeqJjs2ozS7VcIv7it9HmV7LYVntyWgaOVch0JJe6RKQ1U+xXD7Y0jSywk9iTPQe1R429q9uNk3Jnukd/U8UiD0WoFcvte7+ZhESFb5jyZqNEYHCYUDhQyi+Xkm2ha2PQG0hMFrLSabjok9YVO0lU6zroyJpPKs7WpoWaBlfOpqwCfDShKj50gpY5q/xgENkzDX83nKB+WX8BauGqw9GNFKcSZA4ZANHMLoJpqxhNwDqHJg0P9cUd59QfyrftNbr5xwpG+z0Qz/WehC4EuPj2eBn34GF+C02F7T7m6IqQd3x03gh5cyFUP37iQ/KY+CKif+slJMxC86pBVstGSvqAqX+43g/y2P3sQHTMKB+yXsnjkRPIeSAohqCntKDv4CfF1deVLLP5oVikFJOHAPdzVPDawex8hClzxmtVBa1loe44lEDnwHAQwZ+CGYhK6UxdnxEZWpu0om+SqWdiPor9rfY2U6ek6AymMjAci4pZAFgUNbv4saWQo8pXKyyhYfJ1jzwsnAl+tk72HnidVFOkoWkRiWDCiJV4ZwQNnJoKzq+8lE88GbFIc5aa+6a/+W092yWPgRoSwXy6gzDuWmHo6pfStVDxmS8c8e9pSBDyCFX6gN1Qqb1CIXRNcVq+vspwcLrYbs53i7lPUEpVatCIwPmOXvNqzr9C8zstUG5Znjt/p3KAEnRNnD7RmbKxLo6RUIHbn4hsicb74I8bjMHgVoqjAXT0pNCFN5JtPw=="
|
||||
@@ -119,8 +119,9 @@ secrets:
|
||||
REVID_API_KEY: "AgBPAmDtdzfHMbudluOeUZS7RCixfJXaI6vBvEUPQhhtpbNW9sUfDv6waKzBdjgWxIf2EqI4QI7QUVqHxA7fMChSjZjt9Np9z6+nHZhKWTqCCKyVR4Lka8tnkU5M62e+x+T7QoHy6mKsB7FEyQ8/FPxUM/Ddp5ZPTj6sbLn3y2uv1SPxInbd6boXeLEwQQcN/Inrww/lzNzJPec4jlHNwPHugjhZJnlWotvyfhU33Fdt5IEusdqt6CM0vS5N7lkR8KTNAg56VLD89yXVRR2VOtkJWzaQJ4lNSztBgUFNGaYtl7SRNVYnMpT1jhyTcAeO/fAGP4O/8haTlAZbfSsOLub5Af9CIA5vtNpU4zoY0Q2MOtKOJ5OTtbbJxmlWex3zc2wpIwdTLCRyBHxiPphdSBXQPW12s9NX6GVR7WDc3Lcvhi0P4uo+y01kyL64JGRigjvBzCYCqhNGWMtJ+YOy4pwcE89Qaz2/EvHgh37P9O7TQhN3Vo8BGRV0+DlNe6uv3OBitgayub5M34nh+qnNHypErkm29SovnqY1fEFhdOib2nmnE2fEXylZMtMPonBVYtVX9iOJLERQMNOzwDxaKzwPdpNu4GhMNVG6joCDjDqKPp9BfnKDJsn0GgUt3oDl5kRWdYL75HLDa3LDWGj0UiAF1YeQE2SHJjlrIoAVJWDdjwTsFJ1x3sMZTt9gB8KiUIDK/hgt7to/kaIyBUTrQIUv/2hHNYI4KH5/nNFZu3TPJXNrxfU="
|
||||
ANTHROPIC_API_KEY: "AgB+40jZoawJ6HaeyyLZvDp2ByF4EPy2Ce806D/lekwJVmxpYXgkQdLJyav6bt9c1g5eDShJqezx1T+jGV+ApQbhSwmO403nJdYO825Fd3XVJ5K0xfFNt8DOTC9r2egWFvJZL40s/Y24kpr2Mmsqp+Sk9DxMNdYG/Z4PnT8PLWgb3yIYGGPVjlWlHL82gn4/B1bVTk14/cGXX9eSr0ktKKmS2OGLMYUwLT3oYGy4bRq9bH91XwjjdW6vMluNBGYibpi2f4h3nYORaQn42OhzTqON3XUdvNmsw1ZH0raMAJq47SU0lC6Ar9MzwbtUWY3tF6BlTmyx3gPavjQCnXg5cRxmY8JklrynDoyN+SBludzaWDzdAjr4vGPpkOo56RBY+28tnNjmxgyttGIYfFa8DEsrrJJgZZxSUvRqwJc0TWIKRFP1aIHkQ5DclUilFtNfPMezxwcFqrsYTEvtDjsls/E8uTNUN99cVQ2x0PxDsLKr9xVKAKkzzOBEvKEAJy0t5RtRV9A6kc0b16YbjIkFphip4e7HJTWKRvavknw+MXjGXXQrz9+xp8LrjRcgCyZp3BqSo+gsX8KQJSnhiFfKvgt7RdVaUVUA+sn5fIQGPWA5IkbI4gS12BRFDw59+Doc5FbCGUip4jDL8I2bPuNKQZLmSMx93Nu/60WBRhKXYz+GNzLzvhk0IpEI3d2GeWvC61p/f5eKnsLKNLDrc3k8rFHiUmXXhB+oQXMRSUFqq4hen32VVPWRhzT8nefww5Tud21CBg5+87x6WHOnB/A4vw+VuKD3fdeo9tn4HlD3w2funOVu9yv+NaP+MTeHus2PBeab/OtKLH09ezxnhmA="
|
||||
OPENAI_API_KEY: "AgAjPjhje52qw5YSXjRAwoXU0WyDEIAHnz2CjFtSjkpbsvvXFPlPMlOd/y7/dvABoyZHB9Ukxjna6opqV/hK/vHR9ncp9i7cDYX3Rekj+mkA6arMdqdJ0eikAGqWYPieu8RcBn6pHFGmoC8ZZPgk6Eh3Wyi4OCaPfH/O1bTq/RBQU5VDFvYfaeDZmYIu6SkD88pI0lT12Dklk1apsHlS+g3/rpQwDXgemE/pdmcNnt1zS6Ifu4isN7yg1pg1Thja+UiQnEkIiZkvmD39LO8HrwOFt8guJctRZ5gnVxPmSEdJLN089/fj5VXxTO1kTprbh1KeG9RKYS5LEPNYpgcl9/o884qMc/r0/+Cy7gL5R1THrEPHurVg8JfssCq8k0SaEtCElQ2081Scc/0p/k4URpXrsxUKZ8XUTIvYS0y2mEJPAAqaHAkwthY8sizhOwqWWnt8dGbCPwfQ11TiNSMikKIim9Bwm4tKM9aEolROSkivbGqFQQYSkensyp2mTqx7iFYlGBa7Z7PFRBZgPzD2FojWc6o5tLui5Xgi67ukO5WeaBhO6eMd2CuIlXqu+5x3+ixIytp9Jpke4mZKwbjYai4j3iELbzEwbGkjsnDyWNYn+1KnPOogd6i5+YPn09FbQO2Qvg2t3yUP/ePeX+fdRYk7AnS/o6nllqj9GLas48JFUlEx+KSO4qwrflRqPUmfmD3wPDTYR2q6yJzdatLYzdRQxEctFhgvco63uhW1YH+1ei1YuxutYPkIOqUwbgfIC3XiW7Tr3R8Gd3TimJLQM1etR6dwrEaEm0jTCIKUoZ+65OIAeVtcXIwWtwRjjUOtR8k9B2UdFoJOgtfIFzlxwYj4xUJrzRkCLdFD1W362n2+O5n7QdXDjYXn9KmxVUEph2vloeS8IGrBjM/l743A3trFD4CZ9g=="
|
||||
DATABASE_URL: "AgCArShEXLoTAfghyAvdy8R62p0n7Qh3xSVz4ISZ/s94M+3H7rCWNZHpx+58DLAZRJXgkXu94sRiMsAa1SNjg7aAY2gMX5n37xDDDXyELW49SBIcRVPHGbVLZ6GFzXr7JbjsxrLJoOQ6mfbSbioqARj5UnDWxBxBaoLGf0YCHb6BHfT1Vw0a/Ca8GwA6UJHhS/umitNwyrnCsIQBuDgF+RSAyerIpGnppSSpM73AL/aam6S5VaUoyfa0YGr5SWp1pBmV+M39Uis4HkNM65yM6iSy7sGcFTpLOuFvWlZstttGCKyQO4PI+bY1In+/MPdKTl14fTs2uqOZ4XaYVzkz89myh6JPDvXYgYxIv04us0Y8x9at12yB5INvScJpCpLbq3Oa9wEh6guw3HvFU9LWO6zrlwDjAzFmvMHyvufvl6/oaFAFBemh1Umv46f/q15Jb89x889hDwnJZl9xmXRhubBHpLlvcoZvhZTN7nkOV49RwDG97enz29Z2YMlN7A2oK30yKzAjRpXBF5m5yKFZCNzEEgMDHxptfygRdN3Mf3W3Pk5C5IE2/htPnO5pxvlr2f6cI3VufxV0x6TEWD6a/88EAf362McGGBFu6Z0/DnJUMs4emDZTNsIsRZ6LNdEWbsF4eNSpIkghNLzfbeloeIuRZDHafhk1VCBYGEZmsNn3uEQ7wkcw1gcfawnuf/3kXQk="
|
||||
SUPABASE_JWT_SECRET: "AgDCNoyGjkV74NoJTSN+VDpiIl8B7sRzDK3WFAO2wBWdhFSwnO2x4cZ7F9YxiEQgvjslCEn5wCrgqBiO6L3EP4qd0EUibZrx+6xXIK5tRY3YACXf1pQEgRTl5fXTbdlRJgr0+2upe1bB30LecbM/G+goX+RprKmwSxNJfBo/AQQP1l1dJsl4v/yGbEj8WQFD7jTiyCGT8skIX07GUvzZVbAD+DaNmvucuENja90O7q3ACOIJ9VWX9rTJX1V5brrb7y4ge8Iv97bUisH/MtFtZMEBEK1csixdBUC47wtboxn3LnMUaE2OlJZD+9IzEf6TqTJGREyOd+h2V/8BmUYnKRZUVWPURsWLinfbDpyXPmGSBgnfYCd5ZKS5dvwvQwiR6ODlO9E2WOe4xoAHSq57uUxLopcrDsVzZcLiahA02613vYafwc9svg/8dJV2FokKrnQs/bRHlVCa51Vs8z/zCBnHzS6RBq2p2vMU/PbzMfMR2DjzQcvE6hOnyLMlOMpYe67FaHC2cZTayUqNzi5hDNUp19grEqwjdvannOenn/ewTX4yCOVaOoAQ+QBrv1o4g46E18wFnDMoYH+b7PJ0mUf3NSV0jXH+elbW5H6c5WdnmKT+nWQhaVTD4pMAlY8z2W6LddpA7XHfj/lAzboSj+VJqwCrx8AVMKJ4LTbYGTdu9RkcNZLVHkrtafEAYZNnYrIwNZT3SuLWO8X/dGKrrZmOoaaQJZkBuRy1bLdOGXKUIBvf+MDvmApjp/+ScDxl11KifiEO3tO65hNVa2YPjkIf36fJpJ6vtDchsmyowJ9cRhduZsu4PO4D"
|
||||
DATABASE_URL: "AgAfP8iiQGaA68dGVHQuHiKXldqhWungOlLEy6kg6nkKIY6LAwwUJbF59SrsbJ6Lvaq+40XiPSEV6ZjC1JpDyNQyPYzS6hUO9Ev82ViQ2H4Ba62jehBjXufVhabGurHe+F/WsyrXAEY496yX8I3/voy92bR+r0z66jRKHPwI+OXP2CyvdfIz6ziGwInkdfGdP0WRopvmSzbr/atUc1MGVBGuCvNguYWQ3WUwiF38EPObsoYpgV8fuD4trrFE2imHRs23AXMK/ntkqAjZwVWXfZNwaFECT9y1ue04rjDhuoFsL6lhvsK9Xf07mrTzBjdjJl0eCCTxsm0kZTTCwsPSq6H+6w8bjH33M1qeEnORwMuthFy4p0r3e+qlWbhHHwR6ku9wiwzCavDTd27EEfMKkD3zG7NrnbYA4zelHfG2q3/1/PZCeAOsa5jo0EuMTJr4p1Z6deKS4wevzOqJ/FcU1/5T24aKdxhVMnVrF9HKCLKHD+lJLJE8XgdZLFeded234nQfc9MGoBCD6FJvgfJCrjQh8QCSpm1aBKgu795Esff3ZqXJFiq7YCTQTOv/P6RXR5XA/LEqq1m5pcyBDzKixILE1SEbNdeXbYNhe7SbobKpQ9gq3f2ssCRNZGMgJtde6TQFx7J76IE4Eu9oqZefNQxHvh9lH2l0bROWy5NYKHfAejXnVGxIEVnoyRvyFB/HMOc="
|
||||
SUPABASE_JWT_SECRET: "AgAYMdZyP+UhxIdTx6qyRzq9xf1dT7S+DFEC8KSPEFydX9+hAdJVTpprOlgLnqSbfSDmbqcFnCH+aK/6rdRx3HI3v41FogyCNFFxTrfxq1Esk8VuaVh8XrO2xKPd4iGBPZaTrenKlgt89aGdjPJzgl+NlZ5+/BXd95P2uX39DDGr9GJdO14zBt69O+L+Yt7kdd3ZMBjWYibZAzf+YaNIx/M7jjzGLYvxtywMVTrR+6e6GkGQSt5CzBpgk1b6ugPVtFs7PqmMtUqXMQjlrW2u7WVZRWeXO93ukc/TtjO2XUY9JfrgibMf0H81NDDTAAQBNqaDk0LdXsPUo9QGnyeQZTsfAOaeM6lTxX9qCYjneN6pxe60U1BKLURpordRdBs3peAedNJ95GC75qcdSkZE2agjwJvXKs8yy2Ig5eiU/80W27IWPMSLWhMSSf4ixyfkNWM4EfWL45bXlVGvtYaeyqByb0QU1g+II3AukIyO1qOS572y0sGseEv/UlfU2NDBLFejeBZaz4s/20lSyLhP3v1Y9aTs8qWIGl67syFKZoCwPRxwip2v7wIDnlDYXtlxMpQUWDnSUX16zQiVALD3izeDYkd1RViBgdYT/G0tp6lBeV1vnF8tBEGWIl3GJFV0okUflAQ9NIrdC5+BlcQDD08Jn0oGjyje7KE/BfvB1lHT7K+h9rr8B/U8zBSaAe+KFjA8pcjHqXgi4Zx3ayTXdAddyFZd0YqONohEAvXB+BLLdYJVNNXjBFwY62XQ6ojD2ZYWz4m/Wo+/zG0Zm5s/v2VS8UT5qe2Wjs3oGHKIJc6Eo3hVwLefcb7V"
|
||||
SENTRY_DSN: "AgA+X2HkF9b3+p13JS4vG7VY+8p7su6qJ7smoPKqYh44Vpb7J5Eu9ksPNQkcFTDDPT8jAylqsHWUdI0A8u20+a4lqqGkmPN5tCgyBgAL1pIyvPUQjYUbL7A5lTQKlRLJJ+05h5XbkRU7cWR+G4yDUCDj2HcThne0CNDUbDao9D67ekSLUtp6/d0KO45Efao4MLuqISnypPUBGHmAdWGr2z/w7ItXjvUKt3RpH6pSCrGzjlKPKhenKdTsk/NX4Z+ew/JBbHiDQjKCdj0UlXFWH7Q4axaFy0T8tsqf/UN7n/QTalYE+v28isxrHvoR6h7kZETQV/gl0y7DdmTCi8/A1j1+e/9zUx6HvK+C/qGMsKMdNgaaVNSdfFp/yfMgXTUn4HGAls4gjVKSSRaIAbBq32NdKkIvRfocuAGsxInwbrDXLR0nzbHG/U/QhlvfL2gfqKRIVRJtEh99VW/KMMeXZUWR9dNt9gfTMtyzL7eta4oEV+g7sdO/9VjDn5wtic2/7eAxgA7wTEoDA8m0whpHH4VcPLHUfKLTHnRXVu6bykAfBgfEKhJBS8DghvPyu73qL5MREuYkGya4n0RQ73h5ja7mYwI0lsefQszP9Fz1lR+757dhJ6+/E7nNnOE/ShD/8xE0V54pd2IvrRoJmcOsIOZ5w+xWfmN8OyLn7wuEpqEuMHEoisLF9RSp2V5iKbB+fFB4o5P1/VqkNPEFBe0jA4K8DAGX+VdChMpjAI47wF22aj+jmTRf+EY+5l+aEvjyU0G7oUPVzzG8rYa6p+v56zeVsmU4SHIDO75J1cH7tnYDeOxk9fAYZgNplS4gKHVT0w=="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgC07IPlKFO0JYHzO7/H/SVmws9x4mKUd08OQ8VrrvTrwXTmmkKJ0nrbCR1tqSEQTGAFsoTbAtJjVmz04xDsBRbuBuTfbROcD057J+4nKSEvke0rUPnBESiziYQF1Xo0xC80li6OEpiz2ssp5yCF0oddqOkXGR5BfKJ8ZnOS+YkvNla4uE8fbzMUGJU0Erw+/DUOVdIIbF8/P/k55VXbhzPiafMuISc4+vH33/SLeSnUctUiVq43x42buYApt1qb/0cOKElRWtdI1YIx79mbhG9RbE3XinYQ11Fbvo/SeJmTI5YtatH8aesFKSHJy1svzULPEG1Fx6B1wvlROxtiu4Y3WV2YEHOLMXUbrM1CGsqO/W07ohyAGAF/KlhHfYCl/ZqAs+BtuBvPtXXPkxSEj0clqknAW8x8wdJBckPn3OVba0hsmBPcw4NNJPYf9X95jiYMMP+TrrpryTygL0TBcH4agRCu1AXukzdjelvQhRidzV6ZoRxP3e6QjMcqoFW3jx9TAKjj5iZcC9BIIiic3XYCWVv5ei2HuFG2jDBbVDNaNFIOrx/CyrqNIdNiyPX0DH+GilUY4AoZgN/k9syvmWi2sl81yLOt27gGDjGSJTX2whNztcJTDWLgl7t1snj9if+3KzROR/Kmz1XawaBGK2Md+JyiugludHmC6eOO+TKXrV/d02LEZKf0aKnAuAu+gfBGBBagHF5jjkgmCYOGQpALDZWsafqmUsHOIvHmrAEpnKwRCW1JX7Z1pU6SkFm8ceNwsEFPDqIeYbra3k/pHxoSMA5QAO/IafFQdp6NTnYPduJN5yjRh6MSJPIf2U2PFgLnNAug/3Uojk6I6NikcORZtBCHBGd9dtYH3ykQdBSh44ysS3m2o6QVqpbQRYAaUbKEU/wExtZYWK9NzlbuxmRoSKJDAXtjG7Lzgx66p+fZAxYU+MPgxzaoyM53kfqGtLfuikAxswIyvK1+mcq866tyqLFSOwHoQKfez6Q="
|
||||
REDIS_PASSWORD: "AgB7eiUuFQO88vVMI28xfmJsA2QzEb71r3NyDJ/KTNsjqn7ai1KpjVaaTDyr4Xzo1wOhwwwxlhIoeBwf26wPiraJtkjRU9z9Aotvy0u8SXFm05ObhMjJoY2dBvW6ga3KNaunWoTx5e6NbYPGRIgNtRBVN4PH5Lf7Ou5SZBjJBaVWgIT1x71tB2eD2XksOw2mrfaF0WODsQxXDOaF9BJ4Gn7yIT0Nh76Okn9uhesQxvojaqlAIeAKXyrZJwAH5qL3D772rYsISmbHC0bCBgx4dbbtvsr4YgiR387ri7KGfrEqoFH/jzUp5cwsJNyBpWG1n2O0QXYgbMIsmJP6rdD+KTZkLGBz0wgq/JySCZM9hj54dYtLE7LMmpZn7//EKZk7zsV1u9oSciQisWcJqW8El+IMOAZilqSR2NjpI4cb0xR7/gTLLQF33+wnZwbbHghbDwTowkzOZ0i7qt73YkR8MKrlLhLcCGHjhyb50xr1DJl9mVUoyHXvFOj2tQO/273sMNdKpJvNFi9EEhdirzbcuphnaRm5xXYF1CHKtXUp6EvdxgHqEuoGwh5Kt8dtGMJfSJ40LsARZXCFU7CC6g/faPq93K5QB/bwlOdABeOVF/odqXZQAADX3TQwIPMH36XuqwNggWQ8Igy5o1d3Hi84jVChmjid/Wk8DREmkntzDy+4Jxzqx1rPSThyoOvopirY8VA="
|
||||
SUPABASE_SERVICE_ROLE_KEY: "AgCADpjXfdpTDruyK2F4GRNT/Kl+yaI87mQdXDHQo3jOC3gWoOiRlXg70JG3jIi2cWjAXwU8ySjpT87aJdRwsMToeMD78zr0FbOSB2abx7OPTij8zWFSzhIo4cLoEkvLxZO9HXwQc959Cxh5oBcn6WBhJ5XMUxNWALIem9+Lb5Eu1CwxSF0EDrl3znx3Iqw/zUqnAgS+Ob4AAiJwXNO641ja7dAKYkb2NJ/KCBgmSXAaPfxQByuNkGP4iwmQuxhhJQ/N+LRVCu03J6NLPVw22feKKtZxAAroMDn5wPhRdmzBawqbRsejiCb0JNL2yd574CDN5xzsDur/RYkCpTrMWzgnN3F1VcYMuB9FwYazKU3XqviOYtP8Ca4sUQChHQEOFP8n3Nt0Z17zo1NtgRt8IBpXpDeZFgDZU6Zy8EtpHHn05KT8YqyLDms2LfJhduiuyndbZgeIfr7IcxbU4aBafh+J/tfN7Tlj5NFYxFImKQ0NFg5z6W9zKKkfFMo9WUcOOXgwg8+g6xeZUX9g3rNpMBNf2bt0UfNqSIBeAmUZVKHuEqneFONbgtqOP2NKsKsSfvCsnpKgAndv+eL627qWAuDzywWuoAcxsF/Kvo/fQnv1a+7abCr1Qhf61u3DBriGp6TAVhQ9z7iGqvkuviELt97NKekeevCgdjwWpk78iKBCmxJobBTErdX2Xhrqfc5AHteoUBYv3TS7N8ZcOmfVmZc2ulgLLQZZ5hK30w9FFu28bu01ArfKcSp3U21keaC/cGHBNdUWgAbg3wIH+3y3vU2MRHI6T6sFrRsNgJH6b8S+HcOInTsoaLFiRv7SYxGYliV47AEukv4G2G+9XO4i4y9P90u5i7KM+J5FRlR6sfiISPozGHUBe9EAKYQcqaSSGP7FWsyNl6DGq/pDkG8IJYqNr21Sl9N1cdhK/Hdd4J80q05A9f3AyzHjtU4YVcvz4TCKr1FJLugBUsz120cA8FxGXweIQRWCzGvSeGA="
|
||||
REDIS_PASSWORD: "AgB7eiUuFQO88vVMI28xfmJsA2QzEb71r3NyDJ/KTNsjqn7ai1KpjVaaTDyr4Xzo1wOhwwwxlhIoeBwf26wPiraJtkjRU9z9Aotvy0u8SXFm05ObhMjJoY2dBvW6ga3KNaunWoTx5e6NbYPGRIgNtRBVN4PH5Lf7Ou5SZBjJBaVWgIT1x71tB2eD2XksOw2mrfaF0WODsQxXDOaF9BJ4Gn7yIT0Nh76Okn9uhesQxvojaqlAIeAKXyrZJwAH5qL3D772rYsISmbHC0bCBgx4dbbtvsr4YgiR387ri7KGfrEqoFH/jzUp5cwsJNyBpWG1n2O0QXYgbMIsmJP6rdD+KTZkLGBz0wgq/JySCZM9hj54dYtLE7LMmpZn7//EKZk7zsV1u9oSciQisWcJqW8El+IMOAZilqSR2NjpI4cb0xR7/gTLLQF33+wnZwbbHghbDwTowkzOZ0i7qt73YkR8MKrlLhLcCGHjhyb50xr1DJl9mVUoyHXvFOj2tQO/273sMNdKpJvNFi9EEhdirzbcuphnaRm5xXYF1CHKtXUp6EvdxgHqEuoGwh5Kt8dtGMJfSJ40LsARZXCFU7CC6g/faPq93K5QB/bwlOdABeOVF/odqXZQAADX3TQwIPMH36XuqwNggWQ8Igy5o1d3Hi84jVChmjid/Wk8DREmkntzDy+4Jxzqx1rPSThyoOvopirY8VA="
|
||||
ENCRYPTION_KEY: "AgCmQ6fVBbbkm884bZyenUPhrbVBb3+sjOFwekFeeptorwVNDTqfOXtoWzl+W+t13tCqHDn8EqEgOGUPSJutAxfiyKcPo9IFDaMskUzaTGUSx/XUmQzHrI9tKP6doSk8V1Vmwm5PRk1elpMP90aG+TtjG1BLFU8JIozEFEvqcmdB9apnXacBV26vP2Gk709DqAYaCkGpXGfoCKDWNjrF/68W8a6UOaRC/+qHuoWXwi03rY16RdPIRRryHICrpp+l6zJToZFHboSV3UpENjJ4tWUW2Qd8pzE5+ZGiZaHcmqp0WtyBmN8d+7m1q2t5RjJ7DTuVmr+XgS5Eb28aHshZK4gdzlqEsYZSMSwxzTgKrDIPCxPqz+TDPA31fQyR1FfwNxgHztXGluUGPAVxtYVij/CGAkQpISHIjR4FR776lMFkddBaVQfbIyXQhnhEeaV9Swxr1EXG9/Nj6q3n13WmCsMVUZYTk147UCeaTdV6Ec4DRDoTVk4uVgGNsjgRS0wLdnW6naPKndOEz8XpjRtnPz1xNeyLW8SF9DAAoOV+zOrfzbzFaFfXozcExFs2OIDa+D9z57j0wi1Nh8qUoFIFVSjIJ5rOztDlDEd/15P5Zvfm5t/GIJNsIUoCEepirqEnQMqs4/J2ZkhL77qPk4QuvY6AMAvx8pOIh/4Z0Fwf2vJtKhN1lNvq9i7NFoiuiaWSzH/LWcsX8U2ibZ+Do5OspJuFIsP6E1v1Mm9hQdmbEp4XRpb4eBvy2L1a5K9vVw=="
|
||||
@@ -80,4 +80,5 @@ env:
|
||||
BACKEND_CORS_ALLOW_ORIGINS: '["https://platform.agpt.co"]'
|
||||
|
||||
secrets:
|
||||
REDIS_PASSWORD: "AgBl/a138QK7k/GzlkjQHIElN+1GK4ao/kw9AgT7iRC48qlWu2xZQlJiC07r3y8MCaY8CljKpggBYN3CmtzYMoIer/FgKNK0pA7vKGSEMIajRJPKZmxMw9NcfXeLPDNz/9Irw3+7DYMWrATXGTsfuvsurVuxWZQQOSrjhzqmd0PyHsLlzflZXZrTncMOailMIBWggtPUoSFm3ytGHFCEp903XEwtoHzVc+LcV4ZOZ8x6GXx9RXwGzlMI3zy8/w9dw1YpeIR8w9yFMp5OnQjsxPP7leiyInD7HNlT/Edi0zWb+WeoVo4rNQt6spURK46FPJVeFyZX3AerM5oQx0ngAH3s55LktMCwFjEY9PtutudrKeXVk23gQ9C2U+7S3c1ebjOlY3f6sovosaXZDf5wbxxjdCAwfHLSqBlI+lTTnrOK9sijMdk1wdpmmap2cXy3QbQa9r0xzCzFHpwGBrtQhFVUyxQL9TENACXU/jBL7hv57MyUUGKfzYdVPMEy6FagBX5UapZxSyKwSR3Cm8Bo16v+y+I/j2zSoFheUQYLsi3JDjtaEDkZ6RLF1H/tecQ5HWtp6kb4YlC7WZhxR0xcKFoi7Bp5bUKSyDRzbkgVhK5LeRG6BhfvJguRSOevGYslB53GqBt/yH27gMW4QCrwGPeLfO2b3f2QVkmLbAghoHSCw/s79O1BMxGNac7Slo1YhJo="
|
||||
REDIS_PASSWORD: "AgBl/a138QK7k/GzlkjQHIElN+1GK4ao/kw9AgT7iRC48qlWu2xZQlJiC07r3y8MCaY8CljKpggBYN3CmtzYMoIer/FgKNK0pA7vKGSEMIajRJPKZmxMw9NcfXeLPDNz/9Irw3+7DYMWrATXGTsfuvsurVuxWZQQOSrjhzqmd0PyHsLlzflZXZrTncMOailMIBWggtPUoSFm3ytGHFCEp903XEwtoHzVc+LcV4ZOZ8x6GXx9RXwGzlMI3zy8/w9dw1YpeIR8w9yFMp5OnQjsxPP7leiyInD7HNlT/Edi0zWb+WeoVo4rNQt6spURK46FPJVeFyZX3AerM5oQx0ngAH3s55LktMCwFjEY9PtutudrKeXVk23gQ9C2U+7S3c1ebjOlY3f6sovosaXZDf5wbxxjdCAwfHLSqBlI+lTTnrOK9sijMdk1wdpmmap2cXy3QbQa9r0xzCzFHpwGBrtQhFVUyxQL9TENACXU/jBL7hv57MyUUGKfzYdVPMEy6FagBX5UapZxSyKwSR3Cm8Bo16v+y+I/j2zSoFheUQYLsi3JDjtaEDkZ6RLF1H/tecQ5HWtp6kb4YlC7WZhxR0xcKFoi7Bp5bUKSyDRzbkgVhK5LeRG6BhfvJguRSOevGYslB53GqBt/yH27gMW4QCrwGPeLfO2b3f2QVkmLbAghoHSCw/s79O1BMxGNac7Slo1YhJo="
|
||||
SUPABASE_JWT_SECRET: "AgAYMdZyP+UhxIdTx6qyRzq9xf1dT7S+DFEC8KSPEFydX9+hAdJVTpprOlgLnqSbfSDmbqcFnCH+aK/6rdRx3HI3v41FogyCNFFxTrfxq1Esk8VuaVh8XrO2xKPd4iGBPZaTrenKlgt89aGdjPJzgl+NlZ5+/BXd95P2uX39DDGr9GJdO14zBt69O+L+Yt7kdd3ZMBjWYibZAzf+YaNIx/M7jjzGLYvxtywMVTrR+6e6GkGQSt5CzBpgk1b6ugPVtFs7PqmMtUqXMQjlrW2u7WVZRWeXO93ukc/TtjO2XUY9JfrgibMf0H81NDDTAAQBNqaDk0LdXsPUo9QGnyeQZTsfAOaeM6lTxX9qCYjneN6pxe60U1BKLURpordRdBs3peAedNJ95GC75qcdSkZE2agjwJvXKs8yy2Ig5eiU/80W27IWPMSLWhMSSf4ixyfkNWM4EfWL45bXlVGvtYaeyqByb0QU1g+II3AukIyO1qOS572y0sGseEv/UlfU2NDBLFejeBZaz4s/20lSyLhP3v1Y9aTs8qWIGl67syFKZoCwPRxwip2v7wIDnlDYXtlxMpQUWDnSUX16zQiVALD3izeDYkd1RViBgdYT/G0tp6lBeV1vnF8tBEGWIl3GJFV0okUflAQ9NIrdC5+BlcQDD08Jn0oGjyje7KE/BfvB1lHT7K+h9rr8B/U8zBSaAe+KFjA8pcjHqXgi4Zx3ayTXdAddyFZd0YqONohEAvXB+BLLdYJVNNXjBFwY62XQ6ojD2ZYWz4m/Wo+/zG0Zm5s/v2VS8UT5qe2Wjs3oGHKIJc6Eo3hVwLefcb7V"
|
||||
Reference in New Issue
Block a user