This commit is contained in:
SwiftyOS
2025-07-09 15:48:27 +02:00
42 changed files with 2226 additions and 3798 deletions

View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -177,7 +177,7 @@ files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
markers = {main = "python_version == \"3.10\"", dev = "python_full_version < \"3.11.3\""}
markers = {main = "python_version < \"3.11\"", dev = "python_full_version < \"3.11.3\""}
[[package]]
name = "attrs"
@@ -390,7 +390,7 @@ description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "python_version == \"3.10\""
markers = "python_version < \"3.11\""
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
@@ -1667,30 +1667,30 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.11.10"
version = "0.12.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58"},
{file = "ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed"},
{file = "ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad"},
{file = "ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19"},
{file = "ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224"},
{file = "ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1"},
{file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"},
{file = "ruff-0.12.2-py3-none-linux_armv6l.whl", hash = "sha256:093ea2b221df1d2b8e7ad92fc6ffdca40a2cb10d8564477a987b44fd4008a7be"},
{file = "ruff-0.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:09e4cf27cc10f96b1708100fa851e0daf21767e9709e1649175355280e0d950e"},
{file = "ruff-0.12.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ae64755b22f4ff85e9c52d1f82644abd0b6b6b6deedceb74bd71f35c24044cc"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb3a6b2db4d6e2c77e682f0b988d4d61aff06860158fdb413118ca133d57922"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73448de992d05517170fc37169cbca857dfeaeaa8c2b9be494d7bcb0d36c8f4b"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b94317cbc2ae4a2771af641739f933934b03555e51515e6e021c64441532d"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45fc42c3bf1d30d2008023a0a9a0cfb06bf9835b147f11fe0679f21ae86d34b1"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce48f675c394c37e958bf229fb5c1e843e20945a6d962cf3ea20b7a107dcd9f4"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793d8859445ea47591272021a81391350205a4af65a9392401f418a95dfb75c9"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6932323db80484dda89153da3d8e58164d01d6da86857c79f1961934354992da"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6aa7e623a3a11538108f61e859ebf016c4f14a7e6e4eba1980190cacb57714ce"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2a4a20aeed74671b2def096bdf2eac610c7d8ffcbf4fb0e627c06947a1d7078d"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:71a4c550195612f486c9d1f2b045a600aeba851b298c667807ae933478fcef04"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4987b8f4ceadf597c927beee65a5eaf994c6e2b631df963f86d8ad1bdea99342"},
{file = "ruff-0.12.2-py3-none-win32.whl", hash = "sha256:369ffb69b70cd55b6c3fc453b9492d98aed98062db9fec828cdfd069555f5f1a"},
{file = "ruff-0.12.2-py3-none-win_amd64.whl", hash = "sha256:dca8a3b6d6dc9810ed8f328d406516bf4d660c00caeaef36eb831cf4871b0639"},
{file = "ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12"},
{file = "ruff-0.12.2.tar.gz", hash = "sha256:d7b4f55cd6f325cb7621244f19c873c565a08aff5a4ba9c69aa7355f3f7afd3e"},
]
[[package]]
@@ -1823,7 +1823,7 @@ description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "python_version == \"3.10\""
markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
@@ -2176,4 +2176,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10,<4.0"
content-hash = "d92143928a88ca3a56ac200c335910eafac938940022fed8bd0d17c95040b54f"
content-hash = "574057127b05f28c2ae39f7b11aa0d7c52f857655e9223e23a27c9989b2ac10f"

View File

@@ -23,7 +23,7 @@ uvicorn = "^0.34.3"
[tool.poetry.group.dev.dependencies]
redis = "^5.2.1"
ruff = "^0.11.10"
ruff = "^0.12.2"
[build-system]
requires = ["poetry-core"]

View File

@@ -1,105 +0,0 @@
# Example Blocks Using the SDK Provider Pattern
This directory contains example blocks demonstrating how to use the AutoGPT SDK with the provider builder pattern.
## Provider Builder Pattern
The provider builder pattern is the recommended way to configure providers for your blocks. It provides a clean, declarative way to set up authentication, costs, rate limits, and other provider-specific settings.
### Basic Provider Configuration
Create a `_config.py` file in your block directory:
```python
from backend.sdk import BlockCostType, ProviderBuilder
# Configure your provider
my_provider = (
ProviderBuilder("my-service")
.with_api_key("MY_SERVICE_API_KEY", "My Service API Key")
.with_base_cost(1, BlockCostType.RUN)
.build()
)
```
### Using the Provider in Your Block
Import the provider and use its `credentials_field()` method:
```python
from backend.sdk import Block, BlockSchema, CredentialsMetaInput
from ._config import my_provider
class MyBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput = my_provider.credentials_field(
description="Credentials for My Service"
)
```
## Examples in This Directory
### 1. Simple Example Block (`simple_example_block.py`)
- Basic block without authentication
- Shows minimal SDK usage
### 2. Example SDK Block (`example_sdk_block.py`)
- Uses provider builder pattern for API key authentication
- Demonstrates credential handling
### 3. Webhook Example Block (`example_webhook_sdk_block.py`)
- Shows webhook configuration with provider pattern
- Includes webhook manager setup
### 4. Advanced Provider Example (`advanced_provider_example.py`)
- Multiple authentication types (API Key and OAuth)
- Custom API client integration
- Rate limiting configuration
- Advanced provider features
## Benefits of the Provider Pattern
1. **Centralized Configuration**: All provider settings in one place
2. **Type Safety**: Full type hints and IDE support
3. **Reusability**: Share provider config across multiple blocks
4. **Consistency**: Standardized pattern across the codebase
5. **Easy Testing**: Mock providers for unit tests
## Best Practices
1. Always create a `_config.py` file for your provider configurations
2. Use descriptive names for your providers
3. Include proper descriptions in `credentials_field()`
4. Set appropriate base costs for your blocks
5. Configure rate limits if your provider has them
6. Use OAuth when available for better security
## Testing
When testing blocks with providers:
```python
from backend.sdk import APIKeyCredentials, SecretStr
# Create test credentials
test_creds = APIKeyCredentials(
id="test-creds",
provider="my-service",
api_key=SecretStr("test-api-key"),
title="Test API Key",
)
# Use in your tests
block = MyBlock()
async for output_name, output_value in block.run(
MyBlock.Input(
credentials={
"provider": "my-service",
"id": "test-creds",
"type": "api_key",
}
),
credentials=test_creds,
):
# Process outputs
```

View File

@@ -1,83 +0,0 @@
"""
Shared configuration for example blocks using the new SDK pattern.
"""
from backend.sdk import APIKeyCredentials, BlockCostType, ProviderBuilder, SecretStr
# Configure the example service provider
example_service = (
ProviderBuilder("example-service")
.with_api_key("EXAMPLE_SERVICE_API_KEY", "Example Service API Key")
.with_base_cost(1, BlockCostType.RUN)
.build()
)
# Test credentials for example service
EXAMPLE_SERVICE_TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="example-service",
api_key=SecretStr("mock-example-api-key"),
title="Mock Example Service API key",
expires_at=None,
)
EXAMPLE_SERVICE_TEST_CREDENTIALS_INPUT = {
"provider": EXAMPLE_SERVICE_TEST_CREDENTIALS.provider,
"id": EXAMPLE_SERVICE_TEST_CREDENTIALS.id,
"type": EXAMPLE_SERVICE_TEST_CREDENTIALS.type,
"title": EXAMPLE_SERVICE_TEST_CREDENTIALS.title,
}
# Configure the example webhook provider
example_webhook = (
ProviderBuilder(name="examplewebhook")
.with_api_key(
env_var_name="EXAMPLE_WEBHOOK_API_KEY", title="Example Webhook API Key"
)
.with_base_cost(
amount=0, cost_type=BlockCostType.RUN
) # Webhooks typically don't have run costs
.build()
)
# Advanced provider configuration
advanced_service = (
ProviderBuilder(name="advanced-service")
.with_api_key(env_var_name="ADVANCED_API_KEY", title="Advanced Service API Key")
.with_base_cost(amount=2, cost_type=BlockCostType.RUN)
.build()
)
# Example of a provider with custom API client
class CustomAPIProvider:
"""Example custom API client for demonstration."""
def __init__(self, credentials):
self.credentials = credentials
async def request(self, method: str, endpoint: str, **kwargs):
# Example of how to use Requests module:
# from backend.sdk import Requests
# response = await Requests().post(
# url="https://api.example.com" + endpoint,
# headers={
# "Content-Type": "application/json",
# "x-api-key": self.credentials.api_key.get_secret_value()
# },
# json=kwargs.get("data", {})
# )
# return response.json()
# Simulated API request for example
return {"status": "ok", "data": kwargs.get("data", {})}
# Configure provider with custom API client
custom_api = (
ProviderBuilder(name="custom-api")
.with_api_key(env_var_name="CUSTOM_API_KEY", title="Custom API Key")
.with_api_client(factory=lambda creds: CustomAPIProvider(creds))
.with_base_cost(amount=3, cost_type=BlockCostType.RUN)
.build()
)

View File

@@ -1,150 +0,0 @@
"""
Advanced Provider Example using the SDK
This demonstrates more advanced provider configurations including:
1. API Key authentication
2. Custom API client integration
3. Error handling patterns
4. Multiple provider configurations
"""
import logging
from backend.sdk import (
APIKeyCredentials,
Block,
BlockCategory,
BlockOutput,
BlockSchema,
CredentialsMetaInput,
SchemaField,
)
from ._config import advanced_service, custom_api
logger = logging.getLogger(__name__)
class AdvancedProviderBlock(Block):
"""
Advanced example block demonstrating API key authentication
and provider configuration features.
"""
class Input(BlockSchema):
credentials: CredentialsMetaInput = advanced_service.credentials_field(
description="Credentials for Advanced Service",
)
operation: str = SchemaField(
description="Operation to perform",
default="read",
)
data: str = SchemaField(
description="Data to process",
default="",
)
class Output(BlockSchema):
result: str = SchemaField(description="Operation result")
auth_type: str = SchemaField(description="Authentication type used")
success: bool = SchemaField(description="Whether operation succeeded")
def __init__(self):
super().__init__(
id="d0086843-4c6c-4b9a-a490-d0e7b4cb317e",
description="Advanced provider example with multiple auth types",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=AdvancedProviderBlock.Input,
output_schema=AdvancedProviderBlock.Output,
)
async def run(
self,
input_data: Input,
*,
credentials: APIKeyCredentials,
**kwargs,
) -> BlockOutput:
try:
logger.debug(
"Starting AdvancedProviderBlock run with operation: %s",
input_data.operation,
)
# Use API key authentication
_ = (
credentials.api_key.get_secret_value()
) # Would be used in real implementation
logger.debug("Successfully authenticated with API key")
result = f"Performed {input_data.operation} with API key auth"
logger.debug("Operation completed successfully")
yield "result", result
yield "auth_type", "api_key"
yield "success", True
except Exception as e:
logger.error("Error in AdvancedProviderBlock: %s", str(e))
yield "result", f"Error: {str(e)}"
yield "auth_type", "error"
yield "success", False
class CustomAPIBlock(Block):
"""
Example block using a provider with custom API client.
"""
class Input(BlockSchema):
credentials: CredentialsMetaInput = custom_api.credentials_field(
description="Credentials for Custom API",
)
endpoint: str = SchemaField(
description="API endpoint to call",
default="/data",
)
payload: str = SchemaField(
description="Payload to send",
default="{}",
)
class Output(BlockSchema):
response: str = SchemaField(description="API response")
status: str = SchemaField(description="Response status")
def __init__(self):
super().__init__(
id="979ccdfd-db5a-4179-ad57-aeb277999d79",
description="Example using custom API client provider",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=CustomAPIBlock.Input,
output_schema=CustomAPIBlock.Output,
)
async def run(
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
) -> BlockOutput:
try:
logger.debug(
"Starting CustomAPIBlock run with endpoint: %s", input_data.endpoint
)
# Get API client from provider
api_client = custom_api.get_api(credentials)
logger.debug("Successfully obtained API client")
# Make API request
logger.debug("Making API request with payload: %s", input_data.payload)
response = await api_client.request(
method="POST",
endpoint=input_data.endpoint,
data=input_data.payload,
)
logger.debug("Received API response: %s", response)
yield "response", str(response)
yield "status", response.get("status", "unknown")
except Exception as e:
logger.error("Error in CustomAPIBlock: %s", str(e))
yield "response", f"Error: {str(e)}"
yield "status", "error"

View File

@@ -1,166 +0,0 @@
"""
Example Block demonstrating the cost decorator
This shows how to define custom costs for a block using the @cost decorator.
"""
from backend.sdk import (
Block,
BlockCategory,
BlockCost,
BlockCostType,
BlockOutput,
BlockSchema,
CredentialsMetaInput,
SchemaField,
cost,
)
from ._config import example_service
# Example 1: Simple block with fixed cost
@cost(BlockCost(cost_type=BlockCostType.RUN, cost_amount=5))
class FixedCostBlock(Block):
"""Block with a fixed cost per run."""
class Input(BlockSchema):
data: str = SchemaField(description="Input data")
class Output(BlockSchema):
result: str = SchemaField(description="Processed data")
cost: int = SchemaField(description="Cost in credits")
def __init__(self):
super().__init__(
id="96f31d13-d741-46a1-97d4-f7e1c3beb9b5",
description="Example block with fixed cost of 5 credits per run",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=self.Input,
output_schema=self.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
yield "result", f"Processed: {input_data.data}"
yield "cost", 5
# Example 2: Block with tiered costs based on input
@cost(
BlockCost(
cost_type=BlockCostType.RUN,
cost_amount=1,
cost_filter={"tier": "basic"},
),
BlockCost(
cost_type=BlockCostType.RUN,
cost_amount=5,
cost_filter={"tier": "standard"},
),
BlockCost(
cost_type=BlockCostType.RUN,
cost_amount=10,
cost_filter={"tier": "premium"},
),
)
class TieredCostBlock(Block):
"""Block with different costs based on selected tier."""
class Input(BlockSchema):
data: str = SchemaField(description="Input data")
tier: str = SchemaField(
description="Service tier (basic, standard, or premium)",
default="basic",
)
class Output(BlockSchema):
result: str = SchemaField(description="Processed data")
tier_used: str = SchemaField(description="Service tier used")
cost: int = SchemaField(description="Cost in credits")
def __init__(self):
super().__init__(
id="fb30be87-f8f7-4701-86b0-6e8cc8046750",
description="Example block with tiered pricing",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=self.Input,
output_schema=self.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
# Simulate different processing based on tier
tier_costs = {"basic": 1, "standard": 5, "premium": 10}
cost = tier_costs.get(input_data.tier, 1)
yield "result", f"Processed with {input_data.tier} tier: {input_data.data}"
yield "tier_used", input_data.tier
yield "cost", cost
# Example 3: Block that uses provider base cost (no @cost decorator)
class ProviderCostBlock(Block):
"""Block that inherits cost from its provider configuration."""
class Input(BlockSchema):
credentials: CredentialsMetaInput = example_service.credentials_field(
description="Example service credentials",
)
data: str = SchemaField(description="Input data")
class Output(BlockSchema):
result: str = SchemaField(description="Processed data")
provider_used: str = SchemaField(description="Provider name")
def __init__(self):
super().__init__(
id="68668919-91c7-4374-aa27-b130c456319b",
description="Example block using provider base cost (1 credit per run)",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=self.Input,
output_schema=self.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
# This block will use the base cost from example_service (1 credit)
yield "result", f"Processed by provider: {input_data.data}"
yield "provider_used", "example-service"
# Example 4: Block with data-based cost
@cost(
BlockCost(
cost_type=BlockCostType.BYTE,
cost_amount=1, # 1 credit per byte
)
)
class DataBasedCostBlock(Block):
"""Block that charges based on data size."""
class Input(BlockSchema):
data: str = SchemaField(description="Input data")
process_intensive: bool = SchemaField(
description="Use intensive processing",
default=False,
)
class Output(BlockSchema):
result: str = SchemaField(description="Processed data")
data_size: int = SchemaField(description="Data size in bytes")
estimated_cost: str = SchemaField(description="Estimated cost")
def __init__(self):
super().__init__(
id="cd928dc6-75f0-4548-9004-7fae8f4cc677",
description="Example block that charges 1 credit per byte",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=self.Input,
output_schema=self.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
data_size = len(input_data.data.encode("utf-8"))
estimated_cost = data_size * 1.0 # 1 credit per byte
yield "result", f"Processed {data_size} bytes"
yield "data_size", data_size
yield "estimated_cost", f"{estimated_cost:.3f} credits"

View File

@@ -1,97 +0,0 @@
"""
Example Block using the new SDK
This demonstrates:
1. Import from backend.sdk module
2. Auto-registration decorators
3. No external configuration needed
"""
from backend.sdk import (
APIKeyCredentials,
Block,
BlockCategory,
BlockOutput,
BlockSchema,
CredentialsMetaInput,
SchemaField,
)
from ._config import (
EXAMPLE_SERVICE_TEST_CREDENTIALS,
EXAMPLE_SERVICE_TEST_CREDENTIALS_INPUT,
example_service,
)
# Example of a simple service
class ExampleSDKBlock(Block):
"""
Example block demonstrating the new SDK system.
With the new SDK:
- All imports come from 'backend.sdk'
- Costs are registered via @cost_config decorator
- Default credentials via @default_credentials decorator
- Provider name via @provider decorator
- No need to modify any files outside the blocks folder!
"""
class Input(BlockSchema):
credentials: CredentialsMetaInput = example_service.credentials_field(
description="Credentials for Example Service API",
)
text: str = SchemaField(description="Text to process", default="Hello, World!")
max_length: int = SchemaField(
description="Maximum length of output", default=100
)
class Output(BlockSchema):
result: str = SchemaField(description="Processed text result")
length: int = SchemaField(description="Length of the result")
api_key_used: bool = SchemaField(description="Whether API key was used")
error: str = SchemaField(description="Error message if any")
def __init__(self):
super().__init__(
id="83815e8c-1273-418e-a8c2-4c454e042060",
description="Example block showing SDK capabilities with auto-registration",
categories={BlockCategory.TEXT, BlockCategory.BASIC},
input_schema=ExampleSDKBlock.Input,
output_schema=ExampleSDKBlock.Output,
test_input={
"credentials": EXAMPLE_SERVICE_TEST_CREDENTIALS_INPUT,
"text": "Test input",
"max_length": 50,
},
test_output=[
("result", "PROCESSED: Test input"),
("length", 21), # Length of "PROCESSED: Test input"
("api_key_used", True),
],
test_credentials=EXAMPLE_SERVICE_TEST_CREDENTIALS,
)
async def run(
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
) -> BlockOutput:
try:
# Get API key from credentials
api_key = credentials.api_key.get_secret_value()
# Simulate API processing
processed_text = f"PROCESSED: {input_data.text}"
# Truncate if needed
if len(processed_text) > input_data.max_length:
processed_text = processed_text[: input_data.max_length]
yield "result", processed_text
yield "length", len(processed_text)
yield "api_key_used", bool(api_key)
except Exception as e:
yield "error", str(e)
yield "result", ""
yield "length", 0
yield "api_key_used", False

View File

@@ -1,149 +0,0 @@
"""
Example Webhook Block using the new SDK
This demonstrates webhook auto-registration without modifying
files outside the blocks folder.
"""
from enum import Enum
from backend.sdk import (
BaseModel,
BaseWebhooksManager,
Block,
BlockCategory,
BlockOutput,
BlockSchema,
BlockType,
BlockWebhookConfig,
CredentialsMetaInput,
Field,
ProviderName,
SchemaField,
)
from ._config import example_webhook
# Define event filter model
class ExampleEventFilter(BaseModel):
created: bool = Field(default=True, description="Listen for created events")
updated: bool = Field(default=True, description="Listen for updated events")
deleted: bool = Field(default=False, description="Listen for deleted events")
# First, define a simple webhook manager for our example service
class ExampleWebhookManager(BaseWebhooksManager):
"""Example webhook manager for demonstration."""
PROVIDER_NAME = ProviderName.GITHUB # Reuse GitHub for example
class WebhookType(str, Enum):
EXAMPLE = "example"
@classmethod
async def validate_payload(cls, webhook, request) -> tuple[dict, str]:
"""Validate incoming webhook payload."""
payload = await request.json()
event_type = request.headers.get("X-Example-Event", "unknown")
return payload, event_type
async def _register_webhook(
self,
credentials,
webhook_type: str,
resource: str,
events: list[str],
ingress_url: str,
secret: str,
) -> tuple[str, dict]:
"""Register webhook with external service."""
# In real implementation, this would call the external API
return "example-webhook-id", {"registered": True}
async def _deregister_webhook(self, webhook, credentials) -> None:
"""Deregister webhook from external service."""
# In real implementation, this would call the external API
pass
# Now create the webhook block
class ExampleWebhookSDKBlock(Block):
"""
Example webhook block demonstrating webhook capabilities.
"""
class Input(BlockSchema):
credentials: CredentialsMetaInput = example_webhook.credentials_field(
description="Credentials for webhook service",
)
webhook_url: str = SchemaField(
description="URL to receive webhooks (auto-generated)",
default="",
hidden=True,
)
event_filter: ExampleEventFilter = SchemaField(
description="Filter for specific events", default=ExampleEventFilter()
)
payload: dict = SchemaField(
description="Webhook payload data", default={}, hidden=True
)
class Output(BlockSchema):
event_type: str = SchemaField(description="Type of webhook event")
event_data: dict = SchemaField(description="Event payload data")
timestamp: str = SchemaField(description="Event timestamp")
error: str = SchemaField(description="Error message if any")
def __init__(self):
super().__init__(
id="7e50eb33-f854-4b73-99a1-50cad2819ae0",
description="Example webhook block with auto-registration",
categories={BlockCategory.INPUT},
input_schema=ExampleWebhookSDKBlock.Input,
output_schema=ExampleWebhookSDKBlock.Output,
block_type=BlockType.WEBHOOK,
webhook_config=BlockWebhookConfig(
provider=ProviderName.GITHUB, # Using GitHub for example
webhook_type="example",
event_filter_input="event_filter",
resource_format="{event}",
),
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
try:
# Extract webhook payload
payload = input_data.payload
# Get event type and timestamp
event_type = payload.get("action", "unknown")
timestamp = payload.get("timestamp", "")
# Filter events based on event filter settings
event_filter = input_data.event_filter
should_process = False
if event_type == "created" and event_filter.created:
should_process = True
elif event_type == "updated" and event_filter.updated:
should_process = True
elif event_type == "deleted" and event_filter.deleted:
should_process = True
if not should_process:
yield "event_type", "filtered"
yield "event_data", {}
yield "timestamp", timestamp
yield "error", ""
return
yield "event_type", event_type
yield "event_data", payload
yield "timestamp", timestamp
except Exception as e:
yield "error", str(e)
yield "event_type", "error"
yield "event_data", {}
yield "timestamp", ""

View File

@@ -1,48 +0,0 @@
"""
Simple Example Block using the new SDK
This demonstrates the new SDK import pattern.
Before SDK: Multiple complex imports from various modules
After SDK: Single import statement
"""
# === OLD WAY (Before SDK) ===
# from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
# from backend.data.model import SchemaField, CredentialsField, CredentialsMetaInput
# from backend.integrations.providers import ProviderName
# from backend.data.cost import BlockCost, BlockCostType
# from typing import List, Optional, Dict
# from pydantic import SecretStr
# === NEW WAY (With SDK) ===
from backend.sdk import Block, BlockCategory, BlockOutput, BlockSchema, SchemaField
class SimpleExampleBlock(Block):
"""
A simple example block showing the power of the SDK.
Key benefits:
1. Single import: from backend.sdk import *
2. Clean, simple block structure
"""
class Input(BlockSchema):
text: str = SchemaField(description="Input text")
count: int = SchemaField(description="Number of repetitions", default=1)
class Output(BlockSchema):
result: str = SchemaField(description="Output result")
def __init__(self):
super().__init__(
id="4a6db1ae-e9d5-4a57-b8b6-9186174a6dd3",
description="Simple example block using SDK",
categories={BlockCategory.TEXT},
input_schema=SimpleExampleBlock.Input,
output_schema=SimpleExampleBlock.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
result = input_data.text * input_data.count
yield "result", result

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import json
from typing import Any, Dict, Optional, Union
from backend.sdk import OAuth2Credentials, Requests
from backend.sdk import APIKeyCredentials, OAuth2Credentials, Requests
from .models import CreateCommentResponse, CreateIssueResponse, Issue, Project
@@ -24,7 +24,7 @@ class LinearClient:
def __init__(
self,
credentials: Union[OAuth2Credentials, None] = None,
credentials: Union[OAuth2Credentials, APIKeyCredentials, None] = None,
custom_requests: Optional[Requests] = None,
):
if custom_requests:
@@ -33,10 +33,8 @@ class LinearClient:
headers: Dict[str, str] = {
"Content-Type": "application/json",
}
if credentials and isinstance(credentials, OAuth2Credentials):
headers["Authorization"] = (
f"Bearer {credentials.access_token.get_secret_value()}"
)
if credentials:
headers["Authorization"] = credentials.auth_header()
self._requests = Requests(
extra_headers=headers,

View File

@@ -3,24 +3,101 @@ Shared configuration for all Linear blocks using the new SDK pattern.
"""
import os
from enum import Enum
from backend.sdk import BlockCostType, ProviderBuilder
from backend.sdk import (
APIKeyCredentials,
BlockCostType,
OAuth2Credentials,
ProviderBuilder,
SecretStr,
)
from ._oauth import LinearOAuthHandler
# (required) Comma separated list of scopes:
# read - (Default) Read access for the user's account. This scope will always be present.
# write - Write access for the user's account. If your application only needs to create comments, use a more targeted scope
# issues:create - Allows creating new issues and their attachments
# comments:create - Allows creating new issue comments
# timeSchedule:write - Allows creating and modifying time schedules
# admin - Full access to admin level endpoints. You should never ask for this permission unless it's absolutely needed
class LinearScope(str, Enum):
READ = "read"
WRITE = "write"
ISSUES_CREATE = "issues:create"
COMMENTS_CREATE = "comments:create"
TIME_SCHEDULE_WRITE = "timeSchedule:write"
ADMIN = "admin"
# Check if Linear OAuth is configured
client_id = os.getenv("LINEAR_CLIENT_ID")
client_secret = os.getenv("LINEAR_CLIENT_SECRET")
LINEAR_OAUTH_IS_CONFIGURED = bool(client_id and client_secret)
# Build the Linear provider
builder = ProviderBuilder("linear").with_base_cost(1, BlockCostType.RUN)
builder = (
ProviderBuilder("linear")
.with_api_key(env_var_name="LINEAR_API_KEY", title="Linear API Key")
.with_base_cost(1, BlockCostType.RUN)
)
# Linear only supports OAuth authentication
if LINEAR_OAUTH_IS_CONFIGURED:
builder = builder.with_oauth(
LinearOAuthHandler, scopes=["read", "write", "issues:create", "comments:create"]
LinearOAuthHandler,
scopes=[
LinearScope.READ,
LinearScope.WRITE,
LinearScope.ISSUES_CREATE,
LinearScope.COMMENTS_CREATE,
],
client_id_env_var="LINEAR_CLIENT_ID",
client_secret_env_var="LINEAR_CLIENT_SECRET",
)
# Build the provider
linear = builder.build()
TEST_CREDENTIALS_OAUTH = OAuth2Credentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="linear",
title="Mock Linear API key",
username="mock-linear-username",
access_token=SecretStr("mock-linear-access-token"),
access_token_expires_at=None,
refresh_token=SecretStr("mock-linear-refresh-token"),
refresh_token_expires_at=None,
scopes=["mock-linear-scopes"],
)
TEST_CREDENTIALS_API_KEY = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="linear",
title="Mock Linear API key",
api_key=SecretStr("mock-linear-api-key"),
expires_at=None,
)
TEST_CREDENTIALS_INPUT_OAUTH = {
"provider": TEST_CREDENTIALS_OAUTH.provider,
"id": TEST_CREDENTIALS_OAUTH.id,
"type": TEST_CREDENTIALS_OAUTH.type,
"title": TEST_CREDENTIALS_OAUTH.type,
}
TEST_CREDENTIALS_INPUT_API_KEY = {
"provider": TEST_CREDENTIALS_API_KEY.provider,
"id": TEST_CREDENTIALS_API_KEY.id,
"type": TEST_CREDENTIALS_API_KEY.type,
"title": TEST_CREDENTIALS_API_KEY.type,
}

View File

@@ -7,6 +7,7 @@ from typing import Optional
from urllib.parse import urlencode
from backend.sdk import (
APIKeyCredentials,
BaseOAuthHandler,
OAuth2Credentials,
ProviderName,
@@ -37,7 +38,7 @@ class LinearOAuthHandler(BaseOAuthHandler):
self.client_secret = client_secret
self.redirect_uri = redirect_uri
self.auth_base_url = "https://linear.app/oauth/authorize"
self.token_url = "https://api.linear.app/oauth/token"
self.token_url = "https://api.linear.app/oauth/token" # Correct token URL
self.revoke_url = "https://api.linear.app/oauth/revoke"
def get_login_url(
@@ -46,7 +47,7 @@ class LinearOAuthHandler(BaseOAuthHandler):
params = {
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"response_type": "code",
"response_type": "code", # Important: include "response_type"
"scope": ",".join(scopes), # Comma-separated, not space-separated
"state": state,
}
@@ -104,11 +105,13 @@ class LinearOAuthHandler(BaseOAuthHandler):
request_body = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"grant_type": "authorization_code",
"grant_type": "authorization_code", # Ensure grant_type is correct
**params,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
headers = {
"Content-Type": "application/x-www-form-urlencoded"
} # Correct header for token request
response = await Requests().post(
self.token_url, data=request_body, headers=headers
)
@@ -130,7 +133,9 @@ class LinearOAuthHandler(BaseOAuthHandler):
new_credentials = OAuth2Credentials(
provider=self.PROVIDER_NAME,
title=current_credentials.title if current_credentials else None,
username=token_data.get("user", {}).get("name", "Unknown User"),
username=token_data.get("user", {}).get(
"name", "Unknown User"
), # extract name or set appropriate
access_token=token_data["access_token"],
scopes=token_data["scope"].split(
","
@@ -151,14 +156,14 @@ class LinearOAuthHandler(BaseOAuthHandler):
try:
# Create a temporary OAuth2Credentials object for the LinearClient
temp_creds = OAuth2Credentials(
id="temp",
provider=self.PROVIDER_NAME,
title="temp",
access_token=SecretStr(access_token),
scopes=[],
)
linear_client = LinearClient(credentials=temp_creds)
linear_client = LinearClient(
APIKeyCredentials(
api_key=SecretStr(access_token),
title="temp",
provider=self.PROVIDER_NAME,
expires_at=None,
)
) # Temporary credentials for this request
query = """
query Viewer {
@@ -171,6 +176,6 @@ class LinearOAuthHandler(BaseOAuthHandler):
response = await linear_client.query(query)
return response["viewer"]["name"]
except Exception as e:
except Exception as e: # Handle any errors
print(f"Error fetching username: {e}")
return None

View File

@@ -1,4 +1,5 @@
from backend.sdk import (
APIKeyCredentials,
Block,
BlockCategory,
BlockOutput,
@@ -9,7 +10,13 @@ from backend.sdk import (
)
from ._api import LinearAPIException, LinearClient
from ._config import linear
from ._config import (
LINEAR_OAUTH_IS_CONFIGURED,
TEST_CREDENTIALS_INPUT_OAUTH,
TEST_CREDENTIALS_OAUTH,
LinearScope,
linear,
)
from .models import CreateCommentResponse
@@ -19,7 +26,7 @@ class LinearCreateCommentBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput = linear.credentials_field(
description="Linear credentials with comment creation permissions",
required_scopes={"read", "comments:create"},
required_scopes={LinearScope.COMMENTS_CREATE},
)
issue_id: str = SchemaField(description="ID of the issue to comment on")
comment: str = SchemaField(description="Comment text to add to the issue")
@@ -29,22 +36,34 @@ class LinearCreateCommentBlock(Block):
comment_body: str = SchemaField(
description="Text content of the created comment"
)
error: str = SchemaField(
description="Error message if comment creation failed", default=""
)
error: str = SchemaField(description="Error message if comment creation failed")
def __init__(self):
super().__init__(
id="8f7d3a2e-9b5c-4c6a-8f1d-7c8b3e4a5d6c",
description="Creates a new comment on a Linear issue",
input_schema=LinearCreateCommentBlock.Input,
output_schema=LinearCreateCommentBlock.Output,
categories={BlockCategory.PRODUCTIVITY},
input_schema=self.Input,
output_schema=self.Output,
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
test_input={
"issue_id": "TEST-123",
"comment": "Test comment",
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
},
disabled=not LINEAR_OAUTH_IS_CONFIGURED,
test_credentials=TEST_CREDENTIALS_OAUTH,
test_output=[("comment_id", "abc123"), ("comment_body", "Test comment")],
test_mock={
"create_comment": lambda *args, **kwargs: (
"abc123",
"Test comment",
)
},
)
@staticmethod
async def create_comment(
credentials: OAuth2Credentials, issue_id: str, comment: str
credentials: OAuth2Credentials | APIKeyCredentials, issue_id: str, comment: str
) -> tuple[str, str]:
client = LinearClient(credentials=credentials)
response: CreateCommentResponse = await client.try_create_comment(
@@ -56,7 +75,7 @@ class LinearCreateCommentBlock(Block):
self,
input_data: Input,
*,
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
**kwargs,
) -> BlockOutput:
"""Execute the comment creation"""

View File

@@ -1,4 +1,5 @@
from backend.sdk import (
APIKeyCredentials,
Block,
BlockCategory,
BlockOutput,
@@ -9,7 +10,13 @@ from backend.sdk import (
)
from ._api import LinearAPIException, LinearClient
from ._config import linear
from ._config import (
LINEAR_OAUTH_IS_CONFIGURED,
TEST_CREDENTIALS_INPUT_OAUTH,
TEST_CREDENTIALS_OAUTH,
LinearScope,
linear,
)
from .models import CreateIssueResponse, Issue
@@ -19,50 +26,62 @@ class LinearCreateIssueBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput = linear.credentials_field(
description="Linear credentials with issue creation permissions",
required_scopes={"read", "issues:create"},
required_scopes={LinearScope.ISSUES_CREATE},
)
title: str = SchemaField(description="Title of the issue")
description: str = SchemaField(
description="Description of the issue", default=""
)
description: str | None = SchemaField(description="Description of the issue")
team_name: str = SchemaField(
description="Name of the team to create the issue on"
)
priority: int = SchemaField(
description="Priority of the issue (0-4, where 0 is no priority, 1 is urgent, 2 is high, 3 is normal, 4 is low)",
default=3,
priority: int | None = SchemaField(
description="Priority of the issue",
default=None,
ge=0,
le=4,
)
project_name: str = SchemaField(
project_name: str | None = SchemaField(
description="Name of the project to create the issue on",
default="",
default=None,
)
class Output(BlockSchema):
issue_id: str = SchemaField(description="ID of the created issue")
issue_title: str = SchemaField(description="Title of the created issue")
error: str = SchemaField(
description="Error message if issue creation failed", default=""
)
error: str = SchemaField(description="Error message if issue creation failed")
def __init__(self):
super().__init__(
id="f9c68f55-dcca-40a8-8771-abf9601680aa",
description="Creates a new issue on Linear",
disabled=not LINEAR_OAUTH_IS_CONFIGURED,
input_schema=self.Input,
output_schema=self.Output,
categories={BlockCategory.PRODUCTIVITY},
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
test_input={
"title": "Test issue",
"description": "Test description",
"team_name": "Test team",
"project_name": "Test project",
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
},
test_credentials=TEST_CREDENTIALS_OAUTH,
test_output=[("issue_id", "abc123"), ("issue_title", "Test issue")],
test_mock={
"create_issue": lambda *args, **kwargs: (
"abc123",
"Test issue",
)
},
)
@staticmethod
async def create_issue(
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
team_name: str,
title: str,
description: str = "",
priority: int = 3,
project_name: str = "",
description: str | None = None,
priority: int | None = None,
project_name: str | None = None,
) -> tuple[str, str]:
client = LinearClient(credentials=credentials)
team_id = await client.try_get_team_by_name(team_name=team_name)
@@ -76,8 +95,8 @@ class LinearCreateIssueBlock(Block):
response: CreateIssueResponse = await client.try_create_issue(
team_id=team_id,
title=title,
description=description if description else None,
priority=priority if priority != 3 else None,
description=description,
priority=priority,
project_id=project_id,
)
return response.issue.identifier, response.issue.title
@@ -113,17 +132,14 @@ class LinearSearchIssuesBlock(Block):
"""Block for searching issues on Linear"""
class Input(BlockSchema):
term: str = SchemaField(description="Term to search for issues")
credentials: CredentialsMetaInput = linear.credentials_field(
description="Linear credentials with read permissions",
required_scopes={"read"},
required_scopes={LinearScope.READ},
)
term: str = SchemaField(description="Term to search for issues")
class Output(BlockSchema):
issues: list[Issue] = SchemaField(description="List of issues")
error: str = SchemaField(
description="Error message if search failed", default=""
)
def __init__(self):
super().__init__(
@@ -131,12 +147,42 @@ class LinearSearchIssuesBlock(Block):
description="Searches for issues on Linear",
input_schema=self.Input,
output_schema=self.Output,
categories={BlockCategory.PRODUCTIVITY},
disabled=not LINEAR_OAUTH_IS_CONFIGURED,
test_input={
"term": "Test issue",
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
},
test_credentials=TEST_CREDENTIALS_OAUTH,
test_output=[
(
"issues",
[
Issue(
id="abc123",
identifier="abc123",
title="Test issue",
description="Test description",
priority=1,
)
],
)
],
test_mock={
"search_issues": lambda *args, **kwargs: [
Issue(
id="abc123",
identifier="abc123",
title="Test issue",
description="Test description",
priority=1,
)
]
},
)
@staticmethod
async def search_issues(
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
term: str,
) -> list[Issue]:
client = LinearClient(credentials=credentials)
@@ -147,7 +193,7 @@ class LinearSearchIssuesBlock(Block):
self,
input_data: Input,
*,
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
**kwargs,
) -> BlockOutput:
"""Execute the issue search"""

View File

@@ -1,4 +1,5 @@
from backend.sdk import (
APIKeyCredentials,
Block,
BlockCategory,
BlockOutput,
@@ -9,7 +10,13 @@ from backend.sdk import (
)
from ._api import LinearAPIException, LinearClient
from ._config import linear
from ._config import (
LINEAR_OAUTH_IS_CONFIGURED,
TEST_CREDENTIALS_INPUT_OAUTH,
TEST_CREDENTIALS_OAUTH,
LinearScope,
linear,
)
from .models import Project
@@ -19,15 +26,13 @@ class LinearSearchProjectsBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput = linear.credentials_field(
description="Linear credentials with read permissions",
required_scopes={"read"},
required_scopes={LinearScope.READ},
)
term: str = SchemaField(description="Term to search for projects")
class Output(BlockSchema):
projects: list[Project] = SchemaField(description="List of projects")
error: str = SchemaField(
description="Error message if search failed", default=""
)
error: str = SchemaField(description="Error message if issue creation failed")
def __init__(self):
super().__init__(
@@ -35,12 +40,45 @@ class LinearSearchProjectsBlock(Block):
description="Searches for projects on Linear",
input_schema=self.Input,
output_schema=self.Output,
categories={BlockCategory.PRODUCTIVITY},
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
test_input={
"term": "Test project",
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
},
disabled=not LINEAR_OAUTH_IS_CONFIGURED,
test_credentials=TEST_CREDENTIALS_OAUTH,
test_output=[
(
"projects",
[
Project(
id="abc123",
name="Test project",
description="Test description",
priority=1,
progress=1,
content="Test content",
)
],
)
],
test_mock={
"search_projects": lambda *args, **kwargs: [
Project(
id="abc123",
name="Test project",
description="Test description",
priority=1,
progress=1,
content="Test content",
)
]
},
)
@staticmethod
async def search_projects(
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
term: str,
) -> list[Project]:
client = LinearClient(credentials=credentials)
@@ -51,7 +89,7 @@ class LinearSearchProjectsBlock(Block):
self,
input_data: Input,
*,
credentials: OAuth2Credentials,
credentials: OAuth2Credentials | APIKeyCredentials,
**kwargs,
) -> BlockOutput:
"""Execute the project search"""

View File

@@ -127,6 +127,9 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
PERPLEXITY_LLAMA_3_1_SONAR_LARGE_128K_ONLINE = (
"perplexity/llama-3.1-sonar-large-128k-online"
)
PERPLEXITY_SONAR = "perplexity/sonar"
PERPLEXITY_SONAR_PRO = "perplexity/sonar-pro"
PERPLEXITY_SONAR_DEEP_RESEARCH = "perplexity/sonar-deep-research"
QWEN_QWQ_32B_PREVIEW = "qwen/qwq-32b-preview"
NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B = "nousresearch/hermes-3-llama-3.1-405b"
NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B = "nousresearch/hermes-3-llama-3.1-70b"
@@ -229,6 +232,13 @@ MODEL_METADATA = {
LlmModel.PERPLEXITY_LLAMA_3_1_SONAR_LARGE_128K_ONLINE: ModelMetadata(
"open_router", 127072, 127072
),
LlmModel.PERPLEXITY_SONAR: ModelMetadata("open_router", 127000, 127000),
LlmModel.PERPLEXITY_SONAR_PRO: ModelMetadata("open_router", 200000, 8000),
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: ModelMetadata(
"open_router",
128000,
128000,
),
LlmModel.QWEN_QWQ_32B_PREVIEW: ModelMetadata("open_router", 32768, 32768),
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: ModelMetadata(
"open_router", 131000, 4096

View File

@@ -85,6 +85,9 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.EVA_QWEN_2_5_32B: 1,
LlmModel.DEEPSEEK_CHAT: 2,
LlmModel.PERPLEXITY_LLAMA_3_1_SONAR_LARGE_128K_ONLINE: 1,
LlmModel.PERPLEXITY_SONAR: 1,
LlmModel.PERPLEXITY_SONAR_PRO: 5,
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: 10,
LlmModel.QWEN_QWQ_32B_PREVIEW: 2,
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: 1,
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B: 1,

View File

@@ -1,4 +1,6 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from pydantic import BaseModel
from backend.integrations.oauth.todoist import TodoistOAuthHandler
@@ -31,6 +33,27 @@ _handlers_dict = {
}
class SDKAwareCredentials(BaseModel):
"""OAuth credentials configuration."""
use_secrets: bool = True
client_id_env_var: Optional[str] = None
client_secret_env_var: Optional[str] = None
_credentials_by_provider = {}
# Add default credentials for original handlers
for handler in _ORIGINAL_HANDLERS:
provider_name = (
handler.PROVIDER_NAME.value
if hasattr(handler.PROVIDER_NAME, "value")
else str(handler.PROVIDER_NAME)
)
_credentials_by_provider[provider_name] = SDKAwareCredentials(
use_secrets=True, client_id_env_var=None, client_secret_env_var=None
)
# Create a custom dict class that includes SDK handlers
class SDKAwareHandlersDict(dict):
"""Dictionary that automatically includes SDK-registered OAuth handlers."""
@@ -105,7 +128,99 @@ class SDKAwareHandlersDict(dict):
return combined.items()
class SDKAwareCredentialsDict(dict):
"""Dictionary that automatically includes SDK-registered OAuth credentials."""
def __getitem__(self, key):
# First try the original handlers
if key in _credentials_by_provider:
return _credentials_by_provider[key]
# Then try SDK credentials
try:
from backend.sdk import AutoRegistry
sdk_credentials = AutoRegistry.get_oauth_credentials()
if key in sdk_credentials:
# Convert from SDKOAuthCredentials to SDKAwareCredentials
sdk_cred = sdk_credentials[key]
return SDKAwareCredentials(
use_secrets=sdk_cred.use_secrets,
client_id_env_var=sdk_cred.client_id_env_var,
client_secret_env_var=sdk_cred.client_secret_env_var,
)
except ImportError:
pass
# If not found, raise KeyError
raise KeyError(key)
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __contains__(self, key):
if key in _credentials_by_provider:
return True
try:
from backend.sdk import AutoRegistry
sdk_credentials = AutoRegistry.get_oauth_credentials()
return key in sdk_credentials
except ImportError:
return False
def keys(self):
# Combine all keys into a single dict and return its keys view
combined = dict(_credentials_by_provider)
try:
from backend.sdk import AutoRegistry
sdk_credentials = AutoRegistry.get_oauth_credentials()
combined.update(sdk_credentials)
except ImportError:
pass
return combined.keys()
def values(self):
combined = dict(_credentials_by_provider)
try:
from backend.sdk import AutoRegistry
sdk_credentials = AutoRegistry.get_oauth_credentials()
# Convert SDK credentials to SDKAwareCredentials
for key, sdk_cred in sdk_credentials.items():
combined[key] = SDKAwareCredentials(
use_secrets=sdk_cred.use_secrets,
client_id_env_var=sdk_cred.client_id_env_var,
client_secret_env_var=sdk_cred.client_secret_env_var,
)
except ImportError:
pass
return combined.values()
def items(self):
combined = dict(_credentials_by_provider)
try:
from backend.sdk import AutoRegistry
sdk_credentials = AutoRegistry.get_oauth_credentials()
# Convert SDK credentials to SDKAwareCredentials
for key, sdk_cred in sdk_credentials.items():
combined[key] = SDKAwareCredentials(
use_secrets=sdk_cred.use_secrets,
client_id_env_var=sdk_cred.client_id_env_var,
client_secret_env_var=sdk_cred.client_secret_env_var,
)
except ImportError:
pass
return combined.items()
HANDLERS_BY_NAME: dict[str, type["BaseOAuthHandler"]] = SDKAwareHandlersDict()
CREDENTIALS_BY_PROVIDER: dict[str, SDKAwareCredentials] = SDKAwareCredentialsDict()
# --8<-- [end:HANDLERS_BY_NAMEExample]
__all__ = ["HANDLERS_BY_NAME"]

View File

@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
class BaseOAuthHandler(ABC):
# --8<-- [start:BaseOAuthHandler1]
PROVIDER_NAME: ClassVar[ProviderName]
PROVIDER_NAME: ClassVar[ProviderName | str]
DEFAULT_SCOPES: ClassVar[list[str]] = []
# --8<-- [end:BaseOAuthHandler1]
@@ -81,8 +81,6 @@ class BaseOAuthHandler(ABC):
"""Handles the default scopes for the provider"""
# If scopes are empty, use the default scopes for the provider
if not scopes:
logger.debug(
f"Using default scopes for provider {self.PROVIDER_NAME.value}"
)
logger.debug(f"Using default scopes for provider {str(self.PROVIDER_NAME)}")
scopes = self.DEFAULT_SCOPES
return scopes

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ from backend.data.cost import BlockCost, BlockCostType
from backend.data.model import APIKeyCredentials, Credentials, UserPasswordCredentials
from backend.integrations.oauth.base import BaseOAuthHandler
from backend.integrations.webhooks._base import BaseWebhooksManager
from backend.sdk.provider import Provider
from backend.sdk.provider import OAuthConfig, Provider
from backend.sdk.registry import AutoRegistry
from backend.util.settings import Settings
@@ -21,7 +21,7 @@ class ProviderBuilder:
def __init__(self, name: str):
self.name = name
self._oauth_handler: Optional[Type[BaseOAuthHandler]] = None
self._oauth_config: Optional[OAuthConfig] = None
self._webhook_manager: Optional[Type[BaseWebhooksManager]] = None
self._default_credentials: List[Credentials] = []
self._base_costs: List[BlockCost] = []
@@ -29,16 +29,25 @@ class ProviderBuilder:
self._api_client_factory: Optional[Callable] = None
self._error_handler: Optional[Callable[[Exception], str]] = None
self._default_scopes: Optional[List[str]] = None
self._client_id_env_var: Optional[str] = None
self._client_secret_env_var: Optional[str] = None
self._extra_config: dict = {}
def with_oauth(
self, handler_class: Type[BaseOAuthHandler], scopes: Optional[List[str]] = None
self,
handler_class: Type[BaseOAuthHandler],
scopes: Optional[List[str]] = None,
client_id_env_var: Optional[str] = None,
client_secret_env_var: Optional[str] = None,
) -> "ProviderBuilder":
"""Add OAuth support."""
self._oauth_handler = handler_class
self._oauth_config = OAuthConfig(
oauth_handler=handler_class,
scopes=scopes,
client_id_env_var=client_id_env_var,
client_secret_env_var=client_secret_env_var,
)
self._supported_auth_types.add("oauth2")
if scopes:
self._default_scopes = scopes
return self
def with_api_key(self, env_var_name: str, title: str) -> "ProviderBuilder":
@@ -137,7 +146,7 @@ class ProviderBuilder:
"""Build and register the provider configuration."""
provider = Provider(
name=self.name,
oauth_handler=self._oauth_handler,
oauth_config=self._oauth_config,
webhook_manager=self._webhook_manager,
default_credentials=self._default_credentials,
base_costs=self._base_costs,

View File

@@ -43,6 +43,9 @@ def register_provider_costs_for_block(block_class: Type[Block]) -> None:
return
# Look for credentials fields
# The cost system works of filtering on credentials fields,
# without credentials fields, we can not apply costs
# TODO: Improve cost system to allow for costs witout a provider
credentials_fields = input_schema.get_credentials_fields()
if not credentials_fields:
logger.debug(f"Block {block_class.__name__} has no credentials fields")

View File

@@ -4,19 +4,42 @@ Provider configuration class that holds all provider-related settings.
from typing import Any, Callable, List, Optional, Set, Type
from pydantic import BaseModel
from backend.data.cost import BlockCost
from backend.data.model import Credentials, CredentialsField, CredentialsMetaInput
from backend.integrations.oauth.base import BaseOAuthHandler
from backend.integrations.webhooks._base import BaseWebhooksManager
class OAuthConfig(BaseModel):
"""Configuration for OAuth authentication."""
oauth_handler: Type[BaseOAuthHandler]
scopes: Optional[List[str]] = None
client_id_env_var: Optional[str] = None
client_secret_env_var: Optional[str] = None
class Provider:
"""A configured provider that blocks can use."""
"""A configured provider that blocks can use.
A Provider represents a service or platform that blocks can integrate with, like Linear, OpenAI, etc.
It contains configuration for:
- Authentication (OAuth, API keys)
- Default credentials
- Base costs for using the provider
- Webhook handling
- Error handling
- API client factory
Blocks use Provider instances to handle authentication, make API calls, and manage service-specific logic.
"""
def __init__(
self,
name: str,
oauth_handler: Optional[Type[BaseOAuthHandler]] = None,
oauth_config: Optional[OAuthConfig] = None,
webhook_manager: Optional[Type[BaseWebhooksManager]] = None,
default_credentials: Optional[List[Credentials]] = None,
base_costs: Optional[List[BlockCost]] = None,
@@ -26,7 +49,7 @@ class Provider:
**kwargs,
):
self.name = name
self.oauth_handler = oauth_handler
self.oauth_config = oauth_config
self.webhook_manager = webhook_manager
self.default_credentials = default_credentials or []
self.base_costs = base_costs or []

View File

@@ -6,7 +6,7 @@ import logging
import threading
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type
from pydantic import SecretStr
from pydantic import BaseModel, SecretStr
from backend.blocks.basic import Block
from backend.data.model import APIKeyCredentials, Credentials
@@ -17,6 +17,14 @@ if TYPE_CHECKING:
from backend.sdk.provider import Provider
class SDKOAuthCredentials(BaseModel):
"""OAuth credentials configuration for SDK providers."""
use_secrets: bool = False
client_id_env_var: Optional[str] = None
client_secret_env_var: Optional[str] = None
class BlockConfiguration:
"""Configuration associated with a block."""
@@ -42,6 +50,7 @@ class AutoRegistry:
_providers: Dict[str, "Provider"] = {}
_default_credentials: List[Credentials] = []
_oauth_handlers: Dict[str, Type[BaseOAuthHandler]] = {}
_oauth_credentials: Dict[str, SDKOAuthCredentials] = {}
_webhook_managers: Dict[str, Type[BaseWebhooksManager]] = {}
_block_configurations: Dict[Type[Block], BlockConfiguration] = {}
_api_key_mappings: Dict[str, str] = {} # provider -> env_var_name
@@ -53,18 +62,28 @@ class AutoRegistry:
cls._providers[provider.name] = provider
# Register OAuth handler if provided
if provider.oauth_handler:
if provider.oauth_config:
# Dynamically set PROVIDER_NAME if not already set
if (
not hasattr(provider.oauth_handler, "PROVIDER_NAME")
or provider.oauth_handler.PROVIDER_NAME is None
not hasattr(provider.oauth_config.oauth_handler, "PROVIDER_NAME")
or provider.oauth_config.oauth_handler.PROVIDER_NAME is None
):
# Import ProviderName to create dynamic enum value
from backend.integrations.providers import ProviderName
# This works because ProviderName has _missing_ method
provider.oauth_handler.PROVIDER_NAME = ProviderName(provider.name)
cls._oauth_handlers[provider.name] = provider.oauth_handler
provider.oauth_config.oauth_handler.PROVIDER_NAME = ProviderName(
provider.name
)
cls._oauth_handlers[provider.name] = provider.oauth_config.oauth_handler
# Register OAuth credentials configuration
oauth_creds = SDKOAuthCredentials(
use_secrets=False, # SDK providers use custom env vars
client_id_env_var=provider.oauth_config.client_id_env_var,
client_secret_env_var=provider.oauth_config.client_secret_env_var,
)
cls._oauth_credentials[provider.name] = oauth_creds
# Register webhook manager if provided
if provider.webhook_manager:
@@ -116,6 +135,12 @@ class AutoRegistry:
with cls._lock:
return cls._oauth_handlers.copy()
@classmethod
def get_oauth_credentials(cls) -> Dict[str, SDKOAuthCredentials]:
"""Get OAuth credentials configuration for SDK providers."""
with cls._lock:
return cls._oauth_credentials.copy()
@classmethod
def get_webhook_managers(cls) -> Dict[str, Type[BaseWebhooksManager]]:
"""Replace load_webhook_managers() in webhooks/__init__.py."""

View File

@@ -5,15 +5,13 @@ This module provides models that will be included in the OpenAPI schema generati
allowing frontend code generators like Orval to create corresponding TypeScript types.
"""
from typing import List, Literal
from pydantic import BaseModel, Field, create_model
from pydantic import BaseModel, Field
from backend.integrations.providers import ProviderName
from backend.sdk.registry import AutoRegistry
def get_all_provider_names() -> List[str]:
def get_all_provider_names() -> list[str]:
"""
Collect all provider names from both ProviderName enum and AutoRegistry.
@@ -43,40 +41,12 @@ def get_all_provider_names() -> List[str]:
class ProviderNamesResponse(BaseModel):
"""Response containing list of all provider names."""
providers: List[str] = Field(
providers: list[str] = Field(
description="List of all available provider names",
default_factory=get_all_provider_names,
)
def create_provider_enum_model():
"""
Dynamically create a model with all provider names as a Literal type.
This ensures the OpenAPI schema includes all provider names.
"""
all_providers = get_all_provider_names()
if not all_providers:
# Fallback if no providers are registered yet
all_providers = ["unknown"]
# Create a Literal type with all provider names
# This will be included in the OpenAPI schema
ProviderNameLiteral = Literal[tuple(all_providers)] # type: ignore
# Create a dynamic model that uses this Literal
DynamicProviderModel = create_model(
"AllProviderNames",
provider=(
ProviderNameLiteral,
Field(description="A provider name from the complete list"),
),
__module__=__name__,
)
return DynamicProviderModel
class ProviderConstants(BaseModel):
"""
Model that exposes all provider names as a constant in the OpenAPI schema.

View File

@@ -30,7 +30,7 @@ from backend.data.model import (
)
from backend.executor.utils import add_graph_execution
from backend.integrations.creds_manager import IntegrationCredentialsManager
from backend.integrations.oauth import HANDLERS_BY_NAME
from backend.integrations.oauth import CREDENTIALS_BY_PROVIDER, HANDLERS_BY_NAME
from backend.integrations.providers import ProviderName
from backend.integrations.webhooks import get_webhook_manager
from backend.server.integrations.models import (
@@ -496,8 +496,30 @@ def _get_provider_oauth_handler(
detail=f"Provider '{provider_key}' does not support OAuth",
)
client_id = getattr(settings.secrets, f"{provider_name.value}_client_id")
client_secret = getattr(settings.secrets, f"{provider_name.value}_client_secret")
# Check if this provider has custom OAuth credentials
oauth_credentials = CREDENTIALS_BY_PROVIDER.get(provider_key)
if oauth_credentials and not oauth_credentials.use_secrets:
# SDK provider with custom env vars
import os
client_id = (
os.getenv(oauth_credentials.client_id_env_var)
if oauth_credentials.client_id_env_var
else None
)
client_secret = (
os.getenv(oauth_credentials.client_secret_env_var)
if oauth_credentials.client_secret_env_var
else None
)
else:
# Original provider using settings.secrets
client_id = getattr(settings.secrets, f"{provider_name.value}_client_id", None)
client_secret = getattr(
settings.secrets, f"{provider_name.value}_client_secret", None
)
if not (client_id and client_secret):
logger.error(
f"Attempt to use unconfigured {provider_name.value} OAuth integration"

View File

@@ -31,18 +31,18 @@ files = [
[[package]]
name = "aiodns"
version = "3.4.0"
version = "3.5.0"
description = "Simple DNS resolver for asyncio"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "aiodns-3.4.0-py3-none-any.whl", hash = "sha256:4da2b25f7475343f3afbb363a2bfe46afa544f2b318acb9a945065e622f4ed24"},
{file = "aiodns-3.4.0.tar.gz", hash = "sha256:24b0ae58410530367f21234d0c848e4de52c1f16fbddc111726a4ab536ec1b2f"},
{file = "aiodns-3.5.0-py3-none-any.whl", hash = "sha256:6d0404f7d5215849233f6ee44854f2bb2481adf71b336b2279016ea5990ca5c5"},
{file = "aiodns-3.5.0.tar.gz", hash = "sha256:11264edbab51896ecf546c18eb0dd56dff0428c6aa6d2cd87e643e07300eb310"},
]
[package.dependencies]
pycares = ">=4.0.0"
pycares = ">=4.9.0"
[[package]]
name = "aiofiles"
@@ -222,14 +222,14 @@ files = [
[[package]]
name = "anthropic"
version = "0.51.0"
version = "0.57.1"
description = "The official Python library for the anthropic API"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "anthropic-0.51.0-py3-none-any.whl", hash = "sha256:b8b47d482c9aa1f81b923555cebb687c2730309a20d01be554730c8302e0f62a"},
{file = "anthropic-0.51.0.tar.gz", hash = "sha256:6f824451277992af079554430d5b2c8ff5bc059cc2c968cdc3f06824437da201"},
{file = "anthropic-0.57.1-py3-none-any.whl", hash = "sha256:33afc1f395af207d07ff1bffc0a3d1caac53c371793792569c5d2f09283ea306"},
{file = "anthropic-0.57.1.tar.gz", hash = "sha256:7815dd92245a70d21f65f356f33fc80c5072eada87fb49437767ea2918b2c4b0"},
]
[package.dependencies]
@@ -242,6 +242,7 @@ sniffio = "*"
typing-extensions = ">=4.10,<5"
[package.extras]
aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"]
bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"]
vertex = ["google-auth[requests] (>=2,<3)"]
@@ -1005,14 +1006,14 @@ pgp = ["gpg"]
[[package]]
name = "e2b"
version = "1.5.0"
version = "1.5.4"
description = "E2B SDK that give agents cloud environments"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "e2b-1.5.0-py3-none-any.whl", hash = "sha256:875a843d1d314a9945e24bfb78c9b1b5cac7e2ecb1e799664d827a26a0b2276a"},
{file = "e2b-1.5.0.tar.gz", hash = "sha256:905730eea5c07f271d073d4b5d2a9ef44c8ac04b9b146a99fa0235db77bf6854"},
{file = "e2b-1.5.4-py3-none-any.whl", hash = "sha256:9c8d22f9203311dff890e037823596daaba3d793300238117f2efc5426888f2c"},
{file = "e2b-1.5.4.tar.gz", hash = "sha256:49f1c115d0198244beef5854d19cc857fda9382e205f137b98d3dae0e7e0b2d2"},
]
[package.dependencies]
@@ -1026,19 +1027,19 @@ typing-extensions = ">=4.1.0"
[[package]]
name = "e2b-code-interpreter"
version = "1.5.0"
version = "1.5.2"
description = "E2B Code Interpreter - Stateful code execution"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "e2b_code_interpreter-1.5.0-py3-none-any.whl", hash = "sha256:299f5641a3754264a07f8edc3cccb744d6b009f10dc9285789a9352e24989a9b"},
{file = "e2b_code_interpreter-1.5.0.tar.gz", hash = "sha256:cd6028b6f20c4231e88a002de86484b9d4a99ea588b5be183b9ec7189a0f3cf6"},
{file = "e2b_code_interpreter-1.5.2-py3-none-any.whl", hash = "sha256:5c3188d8f25226b28fef4b255447cc6a4c36afb748bdd5180b45be486d5169f3"},
{file = "e2b_code_interpreter-1.5.2.tar.gz", hash = "sha256:3bd6ea70596290e85aaf0a2f19f28bf37a5e73d13086f5e6a0080bb591c5a547"},
]
[package.dependencies]
attrs = ">=21.3.0"
e2b = ">=1.4.0,<2.0.0"
e2b = ">=1.5.4,<2.0.0"
httpx = ">=0.20.0,<1.0.0"
[[package]]
@@ -1109,14 +1110,14 @@ typing-extensions = "*"
[[package]]
name = "fastapi"
version = "0.115.12"
version = "0.115.14"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"},
{file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"},
{file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"},
{file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"},
]
[package.dependencies]
@@ -1192,20 +1193,20 @@ packaging = ">=20"
[[package]]
name = "flake8"
version = "7.2.0"
version = "7.3.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"},
{file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"},
{file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
{file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.13.0,<2.14.0"
pyflakes = ">=3.3.0,<3.4.0"
pycodestyle = ">=2.14.0,<2.15.0"
pyflakes = ">=3.4.0,<3.5.0"
[[package]]
name = "frozenlist"
@@ -1356,14 +1357,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"]
[[package]]
name = "google-api-python-client"
version = "2.170.0"
version = "2.176.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_api_python_client-2.170.0-py3-none-any.whl", hash = "sha256:7bf518a0527ad23322f070fa69f4f24053170d5c766821dc970ff0571ec22748"},
{file = "google_api_python_client-2.170.0.tar.gz", hash = "sha256:75f3a1856f11418ea3723214e0abc59d9b217fd7ed43dcf743aab7f06ab9e2b1"},
{file = "google_api_python_client-2.176.0-py3-none-any.whl", hash = "sha256:e22239797f1d085341e12cd924591fc65c56d08e0af02549d7606092e6296510"},
{file = "google_api_python_client-2.176.0.tar.gz", hash = "sha256:2b451cdd7fd10faeb5dd20f7d992f185e1e8f4124c35f2cdcc77c843139a4cf1"},
]
[package.dependencies]
@@ -1516,27 +1517,27 @@ protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4
[[package]]
name = "google-cloud-storage"
version = "3.1.0"
version = "3.2.0"
description = "Google Cloud Storage API client library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_cloud_storage-3.1.0-py2.py3-none-any.whl", hash = "sha256:eaf36966b68660a9633f03b067e4a10ce09f1377cae3ff9f2c699f69a81c66c6"},
{file = "google_cloud_storage-3.1.0.tar.gz", hash = "sha256:944273179897c7c8a07ee15f2e6466a02da0c7c4b9ecceac2a26017cb2972049"},
{file = "google_cloud_storage-3.2.0-py3-none-any.whl", hash = "sha256:ff7a9a49666954a7c3d1598291220c72d3b9e49d9dfcf9dfaecb301fc4fb0b24"},
{file = "google_cloud_storage-3.2.0.tar.gz", hash = "sha256:decca843076036f45633198c125d1861ffbf47ebf5c0e3b98dcb9b2db155896c"},
]
[package.dependencies]
google-api-core = ">=2.15.0,<3.0.0dev"
google-auth = ">=2.26.1,<3.0dev"
google-cloud-core = ">=2.4.2,<3.0dev"
google-crc32c = ">=1.0,<2.0dev"
google-resumable-media = ">=2.7.2"
requests = ">=2.18.0,<3.0.0dev"
google-api-core = ">=2.15.0,<3.0.0"
google-auth = ">=2.26.1,<3.0.0"
google-cloud-core = ">=2.4.2,<3.0.0"
google-crc32c = ">=1.1.3,<2.0.0"
google-resumable-media = ">=2.7.2,<3.0.0"
requests = ">=2.22.0,<3.0.0"
[package.extras]
protobuf = ["protobuf (<6.0.0dev)"]
tracing = ["opentelemetry-api (>=1.1.0)"]
protobuf = ["protobuf (>=3.20.2,<7.0.0)"]
tracing = ["opentelemetry-api (>=1.1.0,<2.0.0)"]
[[package]]
name = "google-crc32c"
@@ -1744,14 +1745,14 @@ test = ["objgraph", "psutil"]
[[package]]
name = "groq"
version = "0.24.0"
version = "0.29.0"
description = "The official Python library for the groq API"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "groq-0.24.0-py3-none-any.whl", hash = "sha256:0020e6b0b2b267263c9eb7c318deef13c12f399c6525734200b11d777b00088e"},
{file = "groq-0.24.0.tar.gz", hash = "sha256:e821559de8a77fb81d2585b3faec80ff923d6d64fd52339b33f6c94997d6f7f5"},
{file = "groq-0.29.0-py3-none-any.whl", hash = "sha256:03515ec46be1ef1feef0cd9d876b6f30a39ee2742e76516153d84acd7c97f23a"},
{file = "groq-0.29.0.tar.gz", hash = "sha256:109dc4d696c05d44e4c2cd157652c4c6600c3e96f093f6e158facb5691e37847"},
]
[package.dependencies]
@@ -1762,6 +1763,9 @@ pydantic = ">=1.9.0,<3"
sniffio = "*"
typing-extensions = ">=4.10,<5"
[package.extras]
aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"]
[[package]]
name = "grpc-google-iam-v1"
version = "0.14.2"
@@ -2548,14 +2552,14 @@ files = [
[[package]]
name = "mem0ai"
version = "0.1.102"
version = "0.1.114"
description = "Long-term memory for AI Agents"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "mem0ai-0.1.102-py3-none-any.whl", hash = "sha256:1401ccfd2369e2182ce78abb61b817e739fe49508b5a8ad98abcd4f8ad4db0b4"},
{file = "mem0ai-0.1.102.tar.gz", hash = "sha256:7358dba4fbe954b9c3f33204c14df7babaf9067e2eb48241d89a32e6bc774988"},
{file = "mem0ai-0.1.114-py3-none-any.whl", hash = "sha256:dfb7f0079ee282f5d9782e220f6f09707bcf5e107925d1901dbca30d8dd83f9b"},
{file = "mem0ai-0.1.114.tar.gz", hash = "sha256:b27886132eaec78544e8b8b54f0b14a36728f3c99da54cb7cb417150e2fad7e1"},
]
[package.dependencies]
@@ -2568,8 +2572,11 @@ sqlalchemy = ">=2.0.31"
[package.extras]
dev = ["isort (>=5.13.2)", "pytest (>=8.2.2)", "ruff (>=0.6.5)"]
graph = ["langchain-neo4j (>=0.4.0)", "neo4j (>=5.23.1)", "rank-bm25 (>=0.2.2)"]
extras = ["boto3 (>=1.34.0)", "elasticsearch (>=8.0.0)", "langchain-community (>=0.0.0)", "langchain-memgraph (>=0.1.0)", "opensearch-py (>=2.0.0)", "sentence-transformers (>=5.0.0)"]
graph = ["langchain-aws (>=0.2.23)", "langchain-neo4j (>=0.4.0)", "neo4j (>=5.23.1)", "rank-bm25 (>=0.2.2)"]
llms = ["google-genai (>=1.0.0)", "google-generativeai (>=0.3.0)", "groq (>=0.3.0)", "litellm (>=0.1.0)", "ollama (>=0.1.0)", "together (>=0.2.10)", "vertexai (>=0.1.0)"]
test = ["pytest (>=8.2.2)", "pytest-asyncio (>=0.23.7)", "pytest-mock (>=3.14.0)"]
vector-stores = ["azure-search-documents (>=11.4.0b8)", "chromadb (>=0.4.24)", "faiss-cpu (>=1.7.4)", "pinecone (<=7.3.0)", "pinecone-text (>=0.10.0)", "pymochow (>=2.2.9)", "pymongo (>=4.13.2)", "upstash-vector (>=0.1.0)", "vecs (>=0.4.0)", "weaviate-client (>=4.4.0)"]
[[package]]
name = "more-itertools"
@@ -2908,14 +2915,14 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "ollama"
version = "0.4.9"
version = "0.5.1"
description = "The official Python client for Ollama."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "ollama-0.4.9-py3-none-any.whl", hash = "sha256:18c8c85358c54d7f73d6a66cda495b0e3ba99fdb88f824ae470d740fbb211a50"},
{file = "ollama-0.4.9.tar.gz", hash = "sha256:5266d4d29b5089a01489872b8e8f980f018bccbdd1082b3903448af1d5615ce7"},
{file = "ollama-0.5.1-py3-none-any.whl", hash = "sha256:4c8839f35bc173c7057b1eb2cbe7f498c1a7e134eafc9192824c8aecb3617506"},
{file = "ollama-0.5.1.tar.gz", hash = "sha256:5a799e4dc4e7af638b11e3ae588ab17623ee019e496caaf4323efbaa8feeff93"},
]
[package.dependencies]
@@ -2924,14 +2931,14 @@ pydantic = ">=2.9"
[[package]]
name = "openai"
version = "1.82.1"
version = "1.93.2"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "openai-1.82.1-py3-none-any.whl", hash = "sha256:334eb5006edf59aa464c9e932b9d137468d810b2659e5daea9b3a8c39d052395"},
{file = "openai-1.82.1.tar.gz", hash = "sha256:ffc529680018e0417acac85f926f92aa0bbcbc26e82e2621087303c66bc7f95d"},
{file = "openai-1.93.2-py3-none-any.whl", hash = "sha256:5adbbebd48eae160e6d68efc4c0a4f7cb1318a44c62d9fc626cec229f418eab4"},
{file = "openai-1.93.2.tar.gz", hash = "sha256:4a7312b426b5e4c98b78dfa1148b5683371882de3ad3d5f7c8e0c74f3cc90778"},
]
[package.dependencies]
@@ -2945,6 +2952,7 @@ tqdm = ">4"
typing-extensions = ">=4.11,<5"
[package.extras]
aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"]
datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
realtime = ["websockets (>=13,<16)"]
voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"]
@@ -3259,14 +3267,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"]
[[package]]
name = "poethepoet"
version = "0.34.0"
description = "A task runner that works well with poetry."
version = "0.36.0"
description = "A task runner that works well with poetry and uv."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "poethepoet-0.34.0-py3-none-any.whl", hash = "sha256:c472d6f0fdb341b48d346f4ccd49779840c15b30dfd6bc6347a80d6274b5e34e"},
{file = "poethepoet-0.34.0.tar.gz", hash = "sha256:86203acce555bbfe45cb6ccac61ba8b16a5784264484195874da457ddabf5850"},
{file = "poethepoet-0.36.0-py3-none-any.whl", hash = "sha256:693e3c1eae9f6731d3613c3c0c40f747d3c5c68a375beda42e590a63c5623308"},
{file = "poethepoet-0.36.0.tar.gz", hash = "sha256:2217b49cb4e4c64af0b42ff8c4814b17f02e107d38bc461542517348ede25663"},
]
[package.dependencies]
@@ -3492,14 +3500,14 @@ tqdm = "*"
[[package]]
name = "prometheus-client"
version = "0.21.1"
version = "0.22.1"
description = "Python client for the Prometheus monitoring system."
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"},
{file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"},
{file = "prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094"},
{file = "prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28"},
]
[package.extras]
@@ -3783,83 +3791,88 @@ pyasn1 = ">=0.6.1,<0.7.0"
[[package]]
name = "pycares"
version = "4.8.0"
version = "4.9.0"
description = "Python interface for c-ares"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pycares-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f40d9f4a8de398b110fdf226cdfadd86e8c7eb71d5298120ec41cf8d94b0012f"},
{file = "pycares-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:339de06fc849a51015968038d2bbed68fc24047522404af9533f32395ca80d25"},
{file = "pycares-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372a236c1502b9056b0bea195c64c329603b4efa70b593a33b7ae37fbb7fad00"},
{file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03f66a5e143d102ccc204bd4e29edd70bed28420f707efd2116748241e30cb73"},
{file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef50504296cd5fc58cfd6318f82e20af24fbe2c83004f6ff16259adb13afdf14"},
{file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1bc541b627c7951dd36136b18bd185c5244a0fb2af5b1492ffb8acaceec1c5b"},
{file = "pycares-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:938d188ed6bed696099be67ebdcdf121827b9432b17a9ea9e40dc35fd9d85363"},
{file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:327837ffdc0c7adda09c98e1263c64b2aff814eea51a423f66733c75ccd9a642"},
{file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a6b9b8d08c4508c45bd39e0c74e9e7052736f18ca1d25a289365bb9ac36e5849"},
{file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:feac07d5e6d2d8f031c71237c21c21b8c995b41a1eba64560e8cf1e42ac11bc6"},
{file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5bcdbf37012fd2323ca9f2a1074421a9ccf277d772632f8f0ce8c46ec7564250"},
{file = "pycares-4.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e3ebb692cb43fcf34fe0d26f2cf9a0ea53fdfb136463845b81fad651277922db"},
{file = "pycares-4.8.0-cp310-cp310-win32.whl", hash = "sha256:d98447ec0efff3fa868ccc54dcc56e71faff498f8848ecec2004c3108efb4da2"},
{file = "pycares-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:1abb8f40917960ead3c2771277f0bdee1967393b0fdf68743c225b606787da68"},
{file = "pycares-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e25db89005ddd8d9c5720293afe6d6dd92e682fc6bc7a632535b84511e2060d"},
{file = "pycares-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f9665ef116e6ee216c396f5f927756c2164f9f3316aec7ff1a9a1e1e7ec9b2a"},
{file = "pycares-4.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54a96893133471f6889b577147adcc21a480dbe316f56730871028379c8313f3"},
{file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51024b3a69762bd3100d94986a29922be15e13f56f991aaefb41f5bcd3d7f0bb"},
{file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47ff9db50c599e4d965ae3bec99cc30941c1d2b0f078ec816680b70d052dd54a"},
{file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27ef8ff4e0f60ea6769a60d1c3d1d2aefed1d832e7bb83fc3934884e2dba5cdd"},
{file = "pycares-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63511af7a3f9663f562fbb6bfa3591a259505d976e2aba1fa2da13dde43c6ca7"},
{file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:73c3219b47616e6a5ad1810de96ed59721c7751f19b70ae7bf24997a8365408f"},
{file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:da42a45207c18f37be5e491c14b6d1063cfe1e46620eb661735d0cedc2b59099"},
{file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8a068e898bb5dd09cd654e19cd2abf20f93d0cc59d5d955135ed48ea0f806aa1"},
{file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:962aed95675bb66c0b785a2fbbd1bb58ce7f009e283e4ef5aaa4a1f2dc00d217"},
{file = "pycares-4.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce8b1a16c1e4517a82a0ebd7664783a327166a3764d844cf96b1fb7b9dd1e493"},
{file = "pycares-4.8.0-cp311-cp311-win32.whl", hash = "sha256:b3749ddbcbd216376c3b53d42d8b640b457133f1a12b0e003f3838f953037ae7"},
{file = "pycares-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:5ce8a4e1b485b2360ab666c4ea1db97f57ede345a3b566d80bfa52b17e616610"},
{file = "pycares-4.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3273e01a75308ed06d2492d83c7ba476e579a60a24d9f20fe178ce5e9d8d028b"},
{file = "pycares-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fcedaadea1f452911fd29935749f98d144dae758d6003b7e9b6c5d5bd47d1dff"},
{file = "pycares-4.8.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aae6cb33e287e06a4aabcbc57626df682c9a4fa8026207f5b498697f1c2fb562"},
{file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25038b930e5be82839503fb171385b2aefd6d541bc5b7da0938bdb67780467d2"},
{file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc8499b6e7dfbe4af65f6938db710ce9acd1debf34af2cbb93b898b1e5da6a5a"},
{file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4e1c6a68ef56a7622f6176d9946d4e51f3c853327a0123ef35a5380230c84cd"},
{file = "pycares-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7cc8c3c9114b9c84e4062d25ca9b4bddc80a65d0b074c7cb059275273382f89"},
{file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4404014069d3e362abf404c9932d4335bb9c07ba834cfe7d683c725b92e0f9da"},
{file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ee0a58c32ec2a352cef0e1d20335a7caf9871cd79b73be2ca2896fe70f09c9d7"},
{file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:35f32f52b486b8fede3cbebf088f30b01242d0321b5216887c28e80490595302"},
{file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ecbb506e27a3b3a2abc001c77beeccf265475c84b98629a6b3e61bd9f2987eaa"},
{file = "pycares-4.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9392b2a34adbf60cb9e38f4a0d363413ecea8d835b5a475122f50f76676d59dd"},
{file = "pycares-4.8.0-cp312-cp312-win32.whl", hash = "sha256:f0fbefe68403ffcff19c869b8d621c88a6d2cef18d53cf0dab0fa9458a6ca712"},
{file = "pycares-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa8aab6085a2ddfb1b43a06ddf1b498347117bb47cd620d9b12c43383c9c2737"},
{file = "pycares-4.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:358a9a2c6fed59f62788e63d88669224955443048a1602016d4358e92aedb365"},
{file = "pycares-4.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e3e1278967fa8d4a0056be3fcc8fc551b8bad1fc7d0e5172196dccb8ddb036a"},
{file = "pycares-4.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79befb773e370a8f97de9f16f5ea2c7e7fa0e3c6c74fbea6d332bf58164d7d06"},
{file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b00d3695db64ce98a34e632e1d53f5a1cdb25451489f227bec2a6c03ff87ee8"},
{file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37bdc4f2ff0612d60fc4f7547e12ff02cdcaa9a9e42e827bb64d4748994719f1"},
{file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd92c44498ec7a6139888b464b28c49f7ba975933689bd67ea8d572b94188404"},
{file = "pycares-4.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2665a0d810e2bbc41e97f3c3e5ea7950f666b3aa19c5f6c99d6b018ccd2e0052"},
{file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45a629a6470a33478514c566bce50c63f1b17d1c5f2f964c9a6790330dc105fb"},
{file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:47bb378f1773f41cca8e31dcdf009ce4a9b8aff8a30c7267aaff9a099c407ba5"},
{file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fb3feae38458005cc101956e38f16eb3145fff8cd793e35cd4bdef6bf1aa2623"},
{file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:14bc28aeaa66b0f4331ac94455e8043c8a06b3faafd78cc49d4b677bae0d0b08"},
{file = "pycares-4.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62c82b871470f2864a1febf7b96bb1d108ce9063e6d3d43727e8a46f0028a456"},
{file = "pycares-4.8.0-cp313-cp313-win32.whl", hash = "sha256:01afa8964c698c8f548b46d726f766aa7817b2d4386735af1f7996903d724920"},
{file = "pycares-4.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:22f86f81b12ab17b0a7bd0da1e27938caaed11715225c1168763af97f8bb51a7"},
{file = "pycares-4.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:61325d13a95255e858f42a7a1a9e482ff47ef2233f95ad9a4f308a3bd8ecf903"},
{file = "pycares-4.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfec3a7d42336fa46a1e7e07f67000fd4b97860598c59a894c08f81378629e4e"},
{file = "pycares-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65067e4b4f5345688817fff6be06b9b1f4ec3619b0b9ecc639bc681b73f646b"},
{file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0322ad94bbaa7016139b5bbdcd0de6f6feb9d146d69e03a82aaca342e06830a6"},
{file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:456c60f170c997f9a43c7afa1085fced8efb7e13ae49dd5656f998ae13c4bdb4"},
{file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57a2c4c9ce423a85b0e0227409dbaf0d478f5e0c31d9e626768e77e1e887d32f"},
{file = "pycares-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478d9c479108b7527266864c0affe3d6e863492c9bc269217e36100c8fd89b91"},
{file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aed56bca096990ca0aa9bbf95761fc87e02880e04b0845922b5c12ea9abe523f"},
{file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ef265a390928ee2f77f8901c2273c53293157860451ad453ce7f45dd268b72f9"},
{file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a5f17d7a76d8335f1c90a8530c8f1e8bb22e9a1d70a96f686efaed946de1c908"},
{file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:891f981feb2ef34367378f813fc17b3d706ce95b6548eeea0c9fe7705d7e54b1"},
{file = "pycares-4.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4102f6d9117466cc0a1f527907a1454d109cc9e8551b8074888071ef16050fe3"},
{file = "pycares-4.8.0-cp39-cp39-win32.whl", hash = "sha256:d6775308659652adc88c82c53eda59b5e86a154aaba5ad1e287bbb3e0be77076"},
{file = "pycares-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:8bc05462aa44788d48544cca3d2532466fed2cdc5a2f24a43a92b620a61c9d19"},
{file = "pycares-4.8.0.tar.gz", hash = "sha256:2fc2ebfab960f654b3e3cf08a732486950da99393a657f8b44618ad3ed2d39c1"},
{file = "pycares-4.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b8bd9a3ee6e9bc990e1933dc7e7e2f44d4184f49a90fa444297ac12ab6c0c84"},
{file = "pycares-4.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:417a5c20861f35977240ad4961479a6778125bcac21eb2ad1c3aad47e2ff7fab"},
{file = "pycares-4.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab290faa4ea53ce53e3ceea1b3a42822daffce2d260005533293a52525076750"},
{file = "pycares-4.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1df81193084c9717734e4615e8c5074b9852478c9007d1a8bb242f7f580e67"},
{file = "pycares-4.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20c7a6af0c2ccd17cc5a70d76e299a90e7ebd6c4d8a3d7fff5ae533339f61431"},
{file = "pycares-4.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:370f41442a5b034aebdb2719b04ee04d3e805454a20d3f64f688c1c49f9137c3"},
{file = "pycares-4.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:340e4a3bbfd14d73c01ec0793a321b8a4a93f64c508225883291078b7ee17ac8"},
{file = "pycares-4.9.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ec94785856ea4f5556aa18f4c027361ba4b26cb36c4ad97d2105ef4eec68ba"},
{file = "pycares-4.9.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6b7e23a4a9e2039b5d67dfa0499d2d5f114667dc13fb5d7d03eed230c7ac4f"},
{file = "pycares-4.9.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:490c978b0be9d35a253a5e31dd598f6d66b453625f0eb7dc2d81b22b8c3bb3f4"},
{file = "pycares-4.9.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e433faaf07f44e44f1a1b839fee847480fe3db9431509dafc9f16d618d491d0f"},
{file = "pycares-4.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf6d8851a06b79d10089962c9dadcb34dad00bf027af000f7102297a54aaff2e"},
{file = "pycares-4.9.0-cp310-cp310-win32.whl", hash = "sha256:4f803e7d66ac7d8342998b8b07393788991353a46b05bbaad0b253d6f3484ea8"},
{file = "pycares-4.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e17bd32267e3870855de3baed7d0efa6337344d68f44853fd9195c919f39400"},
{file = "pycares-4.9.0-cp310-cp310-win_arm64.whl", hash = "sha256:6b74f75d8e430f9bb11a1cc99b2e328eed74b17d8d4b476de09126f38d419eb9"},
{file = "pycares-4.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16a97ee83ec60d35c7f716f117719932c27d428b1bb56b242ba1c4aa55521747"},
{file = "pycares-4.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:78748521423a211ce699a50c27cc5c19e98b7db610ccea98daad652ace373990"},
{file = "pycares-4.9.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8818b2c7a57d9d6d41e8b64d9ff87992b8ea2522fc0799686725228bc3cff6c5"},
{file = "pycares-4.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96df8990f16013ca5194d6ece19dddb4ef9cd7c3efaab9f196ec3ccd44b40f8d"},
{file = "pycares-4.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61af86fd58b8326e723b0d20fb96b56acaec2261c3a7c9a1c29d0a79659d613a"},
{file = "pycares-4.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec72edb276bda559813cc807bc47b423d409ffab2402417a5381077e9c2c6be1"},
{file = "pycares-4.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832fb122c7376c76cab62f8862fa5e398b9575fb7c9ff6bc9811086441ee64ca"},
{file = "pycares-4.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdcfaef24f771a471671470ccfd676c0366ab6b0616fd8217b8f356c40a02b83"},
{file = "pycares-4.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:52cb056d06ff55d78a8665b97ae948abaaba2ca200ca59b10346d4526bce1e7d"},
{file = "pycares-4.9.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:54985ed3f2e8a87315269f24cb73441622857a7830adfc3a27c675a94c3261c1"},
{file = "pycares-4.9.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:08048e223615d4aef3dac81fe0ea18fb18d6fc97881f1eb5be95bb1379969b8d"},
{file = "pycares-4.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc60037421ce05a409484287b2cd428e1363cca73c999b5f119936bb8f255208"},
{file = "pycares-4.9.0-cp311-cp311-win32.whl", hash = "sha256:62b86895b60cfb91befb3086caa0792b53f949231c6c0c3053c7dfee3f1386ab"},
{file = "pycares-4.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7046b3c80954beaabf2db52b09c3d6fe85f6c4646af973e61be79d1c51589932"},
{file = "pycares-4.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:fcbda3fdf44e94d3962ca74e6ba3dc18c0d7029106f030d61c04c0876f319403"},
{file = "pycares-4.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d68ca2da1001aeccdc81c4a2fb1f1f6cfdafd3d00e44e7c1ed93e3e05437f666"},
{file = "pycares-4.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4f0c8fa5a384d79551a27eafa39eed29529e66ba8fa795ee432ab88d050432a3"},
{file = "pycares-4.9.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb8c428cf3b9c6ff9c641ba50ab6357b4480cd737498733e6169b0ac8a1a89b"},
{file = "pycares-4.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6845bd4a43abf6dab7fedbf024ef458ac3750a25b25076ea9913e5ac5fec4548"},
{file = "pycares-4.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e28f4acc3b97e46610cf164665ebf914f709daea6ced0ca4358ce55bc1c3d6b"},
{file = "pycares-4.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9464a39861840ce35a79352c34d653a9db44f9333af7c9feddb97998d3e00c07"},
{file = "pycares-4.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0611c1bd46d1fc6bdd9305b8850eb84c77df485769f72c574ed7b8389dfbee2"},
{file = "pycares-4.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4fb5a38a51d03b75ac4320357e632c2e72e03fdeb13263ee333a40621415fdc"},
{file = "pycares-4.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:df5edae05fb3e1370ab7639e67e8891fdaa9026cb10f05dbd57893713f7a9cfe"},
{file = "pycares-4.9.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:397123ea53d261007bb0aa7e767ef238778f45026db40bed8196436da2cc73de"},
{file = "pycares-4.9.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bb0d874d0b131b29894fd8a0f842be91ac21d50f90ec04cff4bb3f598464b523"},
{file = "pycares-4.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:497cc03a61ec1585eb17d2cb086a29a6a67d24babf1e9be519b47222916a3b06"},
{file = "pycares-4.9.0-cp312-cp312-win32.whl", hash = "sha256:b46e46313fdb5e82da15478652aac0fd15e1c9f33e08153bad845aa4007d6f84"},
{file = "pycares-4.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:12547a06445777091605a7581da15a0da158058beb8a05a3ebbf7301fd1f58d4"},
{file = "pycares-4.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:f1e10bf1e8eb80b08e5c828627dba1ebc4acd54803bd0a27d92b9063b6aa99d8"},
{file = "pycares-4.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:574d815112a95ab09d75d0a9dc7dea737c06985e3125cf31c32ba6a3ed6ca006"},
{file = "pycares-4.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50e5ab06361d59625a27a7ad93d27e067dc7c9f6aa529a07d691eb17f3b43605"},
{file = "pycares-4.9.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:785f5fd11ff40237d9bc8afa441551bb449e2812c74334d1d10859569e07515c"},
{file = "pycares-4.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e194a500e403eba89b91fb863c917495c5b3dfcd1ce0ee8dc3a6f99a1360e2fc"},
{file = "pycares-4.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:112dd49cdec4e6150a8d95b197e8b6b7b4468a3170b30738ed9b248cb2240c04"},
{file = "pycares-4.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94aa3c2f3eb0aa69160137134775501f06c901188e722aac63d2a210d4084f99"},
{file = "pycares-4.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b510d71255cf5a92ccc2643a553548fcb0623d6ed11c8c633b421d99d7fa4167"},
{file = "pycares-4.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5c6aa30b1492b8130f7832bf95178642c710ce6b7ba610c2b17377f77177e3cd"},
{file = "pycares-4.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5767988e044faffe2aff6a76aa08df99a8b6ef2641be8b00ea16334ce5dea93"},
{file = "pycares-4.9.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b9928a942820a82daa3207509eaba9e0fa9660756ac56667ec2e062815331fcb"},
{file = "pycares-4.9.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:556c854174da76d544714cdfab10745ed5d4b99eec5899f7b13988cd26ff4763"},
{file = "pycares-4.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d42e2202ca9aa9a0a9a6e43a4a4408bbe0311aaa44800fa27b8fd7f82b20152a"},
{file = "pycares-4.9.0-cp313-cp313-win32.whl", hash = "sha256:cce8ef72c9ed4982c84114e6148a4e42e989d745de7862a0ad8b3f1cdc05def2"},
{file = "pycares-4.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:318cdf24f826f1d2f0c5a988730bd597e1683296628c8f1be1a5b96643c284fe"},
{file = "pycares-4.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:faa9de8e647ed06757a2c117b70a7645a755561def814da6aca0d766cf71a402"},
{file = "pycares-4.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8310d27d68fa25be9781ce04d330f4860634a2ac34dd9265774b5f404679b41f"},
{file = "pycares-4.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99cf98452d3285307eec123049f2c9c50b109e06751b0727c6acefb6da30c6a0"},
{file = "pycares-4.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ffd6e8c8250655504602b076f106653e085e6b1e15318013442558101aa4777"},
{file = "pycares-4.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4065858d8c812159c9a55601fda73760d9e5e3300f7868d9e546eab1084f36c"},
{file = "pycares-4.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91ee6818113faf9013945c2b54bcd6b123d0ac192ae3099cf4288cedaf2dbb25"},
{file = "pycares-4.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21f0602059ec11857ab7ad608c7ec8bc6f7a302c04559ec06d33e82f040585f8"},
{file = "pycares-4.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e22e5b46ed9b12183091da56e4a5a20813b5436c4f13135d7a1c20a84027ca8a"},
{file = "pycares-4.9.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9eded8649867bfd7aea7589c5755eae4d37686272f6ed7a995da40890d02de71"},
{file = "pycares-4.9.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f71d31cbbe066657a2536c98aad850724a9ab7b1cd2624f491832ae9667ea8e7"},
{file = "pycares-4.9.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2b30945982ab4741f097efc5b0853051afc3c11df26996ed53a700c7575175af"},
{file = "pycares-4.9.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54a8f1f067d64810426491d33033f5353b54f35e5339126440ad4e6afbf3f149"},
{file = "pycares-4.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:41556a269a192349e92eee953f62eddd867e9eddb27f444b261e2c1c4a4a9eff"},
{file = "pycares-4.9.0-cp39-cp39-win32.whl", hash = "sha256:524d6c14eaa167ed098a4fe54856d1248fa20c296cdd6976f9c1b838ba32d014"},
{file = "pycares-4.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:15f930c733d36aa487b4ad60413013bd811281b5ea4ca620070fa38505d84df4"},
{file = "pycares-4.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:79b7addb2a41267d46650ac0d9c4f3b3233b036f186b85606f7586881dfb4b69"},
{file = "pycares-4.9.0.tar.gz", hash = "sha256:8ee484ddb23dbec4d88d14ed5b6d592c1960d2e93c385d5e52b6fad564d82395"},
]
[package.dependencies]
@@ -3870,14 +3883,14 @@ idna = ["idna (>=2.1)"]
[[package]]
name = "pycodestyle"
version = "2.13.0"
version = "2.14.0"
description = "Python style guide checker"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"},
{file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"},
{file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
{file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
]
[[package]]
@@ -3894,14 +3907,14 @@ files = [
[[package]]
name = "pydantic"
version = "2.11.5"
version = "2.11.7"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"},
{file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"},
{file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
{file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
]
[package.dependencies]
@@ -4029,14 +4042,14 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-settings"
version = "2.9.1"
version = "2.10.1"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef"},
{file = "pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268"},
{file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"},
{file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"},
]
[package.dependencies]
@@ -4053,16 +4066,31 @@ yaml = ["pyyaml (>=6.0.1)"]
[[package]]
name = "pyflakes"
version = "3.3.2"
version = "3.4.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"},
{file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"},
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
]
[[package]]
name = "pygments"
version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyjwt"
version = "2.10.1"
@@ -4122,14 +4150,14 @@ files = [
[[package]]
name = "pyright"
version = "1.1.401"
version = "1.1.402"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "pyright-1.1.401-py3-none-any.whl", hash = "sha256:6fde30492ba5b0d7667c16ecaf6c699fab8d7a1263f6a18549e0b00bf7724c06"},
{file = "pyright-1.1.401.tar.gz", hash = "sha256:788a82b6611fa5e34a326a921d86d898768cddf59edde8e93e56087d277cc6f1"},
{file = "pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982"},
{file = "pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683"},
]
[package.dependencies]
@@ -4143,26 +4171,27 @@ nodejs = ["nodejs-wheel-binaries"]
[[package]]
name = "pytest"
version = "8.3.5"
version = "8.4.1"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
{file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
{file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1"
packaging = ">=20"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
@@ -4249,14 +4278,14 @@ six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "1.1.0"
version = "1.1.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"},
{file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"},
{file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
{file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
]
[package.extras]
@@ -4702,19 +4731,19 @@ typing_extensions = ">=4.5.0"
[[package]]
name = "requests"
version = "2.32.3"
version = "2.32.4"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
{file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
{file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
]
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
@@ -4900,30 +4929,30 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.11.12"
version = "0.12.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc"},
{file = "ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3"},
{file = "ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be"},
{file = "ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd"},
{file = "ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef"},
{file = "ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5"},
{file = "ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02"},
{file = "ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c"},
{file = "ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6"},
{file = "ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832"},
{file = "ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5"},
{file = "ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603"},
{file = "ruff-0.12.2-py3-none-linux_armv6l.whl", hash = "sha256:093ea2b221df1d2b8e7ad92fc6ffdca40a2cb10d8564477a987b44fd4008a7be"},
{file = "ruff-0.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:09e4cf27cc10f96b1708100fa851e0daf21767e9709e1649175355280e0d950e"},
{file = "ruff-0.12.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ae64755b22f4ff85e9c52d1f82644abd0b6b6b6deedceb74bd71f35c24044cc"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb3a6b2db4d6e2c77e682f0b988d4d61aff06860158fdb413118ca133d57922"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73448de992d05517170fc37169cbca857dfeaeaa8c2b9be494d7bcb0d36c8f4b"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b94317cbc2ae4a2771af641739f933934b03555e51515e6e021c64441532d"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45fc42c3bf1d30d2008023a0a9a0cfb06bf9835b147f11fe0679f21ae86d34b1"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce48f675c394c37e958bf229fb5c1e843e20945a6d962cf3ea20b7a107dcd9f4"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793d8859445ea47591272021a81391350205a4af65a9392401f418a95dfb75c9"},
{file = "ruff-0.12.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6932323db80484dda89153da3d8e58164d01d6da86857c79f1961934354992da"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6aa7e623a3a11538108f61e859ebf016c4f14a7e6e4eba1980190cacb57714ce"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2a4a20aeed74671b2def096bdf2eac610c7d8ffcbf4fb0e627c06947a1d7078d"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:71a4c550195612f486c9d1f2b045a600aeba851b298c667807ae933478fcef04"},
{file = "ruff-0.12.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4987b8f4ceadf597c927beee65a5eaf994c6e2b631df963f86d8ad1bdea99342"},
{file = "ruff-0.12.2-py3-none-win32.whl", hash = "sha256:369ffb69b70cd55b6c3fc453b9492d98aed98062db9fec828cdfd069555f5f1a"},
{file = "ruff-0.12.2-py3-none-win_amd64.whl", hash = "sha256:dca8a3b6d6dc9810ed8f328d406516bf4d660c00caeaef36eb831cf4871b0639"},
{file = "ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12"},
{file = "ruff-0.12.2.tar.gz", hash = "sha256:d7b4f55cd6f325cb7621244f19c873c565a08aff5a4ba9c69aa7355f3f7afd3e"},
]
[[package]]
@@ -4957,14 +4986,14 @@ files = [
[[package]]
name = "sentry-sdk"
version = "2.29.1"
version = "2.32.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19"},
{file = "sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d"},
{file = "sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345"},
{file = "sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b"},
]
[package.dependencies]
@@ -5251,23 +5280,23 @@ typing-extensions = {version = ">=4.5.0", markers = "python_version >= \"3.7\""}
[[package]]
name = "supabase"
version = "2.15.1"
version = "2.16.0"
description = "Supabase client for Python."
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "supabase-2.15.1-py3-none-any.whl", hash = "sha256:749299cdd74ecf528f52045c1e60d9dba81cc2054656f754c0ca7fba0dd34827"},
{file = "supabase-2.15.1.tar.gz", hash = "sha256:66e847dab9346062aa6a25b4e81ac786b972c5d4299827c57d1d5bd6a0346070"},
{file = "supabase-2.16.0-py3-none-any.whl", hash = "sha256:99065caab3d90a56650bf39fbd0e49740995da3738ab28706c61bd7f2401db55"},
{file = "supabase-2.16.0.tar.gz", hash = "sha256:98f3810158012d4ec0e3083f2e5515f5e10b32bd71e7d458662140e963c1d164"},
]
[package.dependencies]
gotrue = ">=2.11.0,<3.0.0"
httpx = ">=0.26,<0.29"
postgrest = ">0.19,<1.1"
realtime = ">=2.4.0,<2.5.0"
storage3 = ">=0.10,<0.12"
supafunc = ">=0.9,<0.10"
postgrest = ">0.19,<1.2"
realtime = ">=2.4.0,<2.6.0"
storage3 = ">=0.10,<0.13"
supafunc = ">=0.9,<0.11"
[[package]]
name = "supafunc"
@@ -5474,14 +5503,14 @@ files = [
[[package]]
name = "tweepy"
version = "4.15.0"
description = "Twitter library for Python"
version = "4.16.0"
description = "Library for accessing the X API (Twitter)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "tweepy-4.15.0-py3-none-any.whl", hash = "sha256:64adcea317158937059e4e2897b3ceb750b0c2dd5df58938c2da8f7eb3b88e6a"},
{file = "tweepy-4.15.0.tar.gz", hash = "sha256:1345cbcdf0a75e2d89f424c559fd49fda4d8cd7be25cd5131e3b57bad8a21d76"},
{file = "tweepy-4.16.0-py3-none-any.whl", hash = "sha256:48d1a1eb311d2c4b8990abcfa6f9fa2b2ad61be05c723b1a9b4f242656badae2"},
{file = "tweepy-4.16.0.tar.gz", hash = "sha256:1d95cbdc50bf6353a387f881f2584eaf60d14e00dbbdd8872a73de79c66878e3"},
]
[package.dependencies]
@@ -5492,8 +5521,6 @@ requests-oauthlib = ">=1.2.0,<3"
[package.extras]
async = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<3)"]
dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"]
docs = ["myst-parser (==0.15.2)", "readthedocs-sphinx-search (==0.1.1)", "sphinx (==4.2.0)", "sphinx-hoverxref (==0.7b1)", "sphinx-tabs (==3.2.0)", "sphinx_rtd_theme (==1.0.0)"]
socks = ["requests[socks] (>=2.27.0,<3)"]
test = ["urllib3 (<2)", "vcrpy (>=1.10.3)"]
[[package]]
@@ -6253,14 +6280,14 @@ requests = "*"
[[package]]
name = "zerobouncesdk"
version = "1.1.1"
version = "1.1.2"
description = "ZeroBounce Python API - https://www.zerobounce.net."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "zerobouncesdk-1.1.1-py3-none-any.whl", hash = "sha256:9fb9dfa44fe4ce35d6f2e43d5144c31ca03544a3317d75643cb9f86b0c028675"},
{file = "zerobouncesdk-1.1.1.tar.gz", hash = "sha256:00aa537263d5bc21534c0007dd9f94ce8e0986caa530c5a0bbe0bd917451f236"},
{file = "zerobouncesdk-1.1.2-py3-none-any.whl", hash = "sha256:a89febfb3adade01c314e6bad2113ad093f1e1cca6ddf9fcf445a8b2a9a458b4"},
{file = "zerobouncesdk-1.1.2.tar.gz", hash = "sha256:24810a2e39c963bc75b4732356b0fc8b10091f2c892f0c8b08fbb32640fdccaf"},
]
[package.dependencies]
@@ -6402,4 +6429,4 @@ cffi = ["cffi (>=1.11)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10,<3.13"
content-hash = "b5c1201f27ee8d05d5d8c89702123df4293f124301d1aef7451591a351872260"
content-hash = "476228d2bf59b90edc5425c462c1263cbc1f2d346f79a826ac5e7efe7823aaa6"

View File

@@ -10,61 +10,61 @@ packages = [{ include = "backend", format = "sdist" }]
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
aio-pika = "^9.5.5"
aiodns = "^3.1.1"
anthropic = "^0.51.0"
aiodns = "^3.5.0"
anthropic = "^0.57.1"
apscheduler = "^3.11.0"
autogpt-libs = { path = "../autogpt_libs", develop = true }
bleach = { extras = ["css"], version = "^6.2.0" }
click = "^8.2.0"
cryptography = "^43.0"
discord-py = "^2.5.2"
e2b-code-interpreter = "^1.5.0"
fastapi = "^0.115.12"
e2b-code-interpreter = "^1.5.2"
fastapi = "^0.115.14"
feedparser = "^6.0.11"
flake8 = "^7.2.0"
google-api-python-client = "^2.169.0"
flake8 = "^7.3.0"
google-api-python-client = "^2.176.0"
google-auth-oauthlib = "^1.2.2"
google-cloud-storage = "^3.1.0"
google-cloud-storage = "^3.2.0"
googlemaps = "^4.10.0"
gravitasml = "^0.1.3"
groq = "^0.24.0"
groq = "^0.29.0"
jinja2 = "^3.1.6"
jsonref = "^1.1.0"
jsonschema = "^4.22.0"
launchdarkly-server-sdk = "^9.11.0"
mem0ai = "^0.1.98"
mem0ai = "^0.1.114"
moviepy = "^2.1.2"
ollama = "^0.4.8"
openai = "^1.78.1"
ollama = "^0.5.1"
openai = "^1.93.2"
pika = "^1.3.2"
pinecone = "^5.3.1"
poetry = "2.1.1" # CHECK DEPENDABOT SUPPORT BEFORE UPGRADING
postmarker = "^1.0"
praw = "~7.8.1"
prisma = "^0.15.0"
prometheus-client = "^0.21.1"
prometheus-client = "^0.22.1"
psutil = "^7.0.0"
psycopg2-binary = "^2.9.10"
pydantic = { extras = ["email"], version = "^2.11.4" }
pydantic-settings = "^2.9.1"
pytest = "^8.3.5"
pydantic = { extras = ["email"], version = "^2.11.7" }
pydantic-settings = "^2.10.1"
pytest = "^8.4.1"
pytest-asyncio = "^0.26.0"
python-dotenv = "^1.1.0"
python-dotenv = "^1.1.1"
python-multipart = "^0.0.20"
redis = "^5.2.0"
replicate = "^1.0.6"
sentry-sdk = {extras = ["anthropic", "fastapi", "launchdarkly", "openai", "sqlalchemy"], version = "^2.28.0"}
sentry-sdk = {extras = ["anthropic", "fastapi", "launchdarkly", "openai", "sqlalchemy"], version = "^2.32.0"}
sqlalchemy = "^2.0.40"
strenum = "^0.4.9"
stripe = "^11.5.0"
supabase = "2.15.1"
supabase = "2.16.0"
tenacity = "^9.1.2"
todoist-api-python = "^2.1.7"
tweepy = "^4.14.0"
tweepy = "^4.16.0"
uvicorn = { extras = ["standard"], version = "^0.34.2" }
websockets = "^14.2"
youtube-transcript-api = "^0.6.2"
zerobouncesdk = "^1.1.1"
zerobouncesdk = "^1.1.2"
# NOTE: please insert new dependencies in their alphabetical location
pytest-snapshot = "^0.9.0"
aiofiles = "^24.1.0"
@@ -78,12 +78,12 @@ black = "^24.10.0"
faker = "^33.3.1"
httpx = "^0.28.1"
isort = "^5.13.2"
poethepoet = "^0.34.0"
pyright = "^1.1.400"
poethepoet = "^0.36.0"
pyright = "^1.1.402"
pytest-mock = "^3.14.0"
pytest-watcher = "^0.4.2"
requests = "^2.32.3"
ruff = "^0.11.10"
requests = "^2.32.4"
ruff = "^0.12.2"
# NOTE: please insert new dependencies in their alphabetical location
[build-system]

View File

@@ -1,146 +0,0 @@
"""
Test the cost integration functionality.
"""
import pytest
from backend.blocks.examples.cost_example_block import (
DataBasedCostBlock,
FixedCostBlock,
ProviderCostBlock,
TieredCostBlock,
)
from backend.data.block_cost_config import BLOCK_COSTS
from backend.data.cost import BlockCost, BlockCostType
from backend.executor.utils import block_usage_cost
from backend.sdk.cost_integration import (
get_block_costs,
register_provider_costs_for_block,
)
class TestCostIntegration:
"""Test cost integration features."""
def test_fixed_cost_block(self):
"""Test block with fixed cost decorator."""
_ = FixedCostBlock() # Instantiate to register costs
# Check that cost was registered
assert FixedCostBlock in BLOCK_COSTS
costs = BLOCK_COSTS[FixedCostBlock]
assert len(costs) == 1
assert costs[0].cost_type == BlockCostType.RUN
assert costs[0].cost_amount == 5
def test_tiered_cost_block(self):
"""Test block with tiered costs."""
_ = TieredCostBlock() # Instantiate to register costs
# Check that all tier costs were registered
assert TieredCostBlock in BLOCK_COSTS
costs = BLOCK_COSTS[TieredCostBlock]
assert len(costs) == 3
# Check each tier
tier_costs = {c.cost_filter.get("tier"): c.cost_amount for c in costs}
assert tier_costs["basic"] == 1
assert tier_costs["standard"] == 5
assert tier_costs["premium"] == 10
def test_data_based_cost_block(self):
"""Test block with data-based (per byte) costs."""
_ = DataBasedCostBlock() # Instantiate to register costs
# Check that cost was registered
assert DataBasedCostBlock in BLOCK_COSTS
costs = BLOCK_COSTS[DataBasedCostBlock]
assert len(costs) == 1
assert costs[0].cost_type == BlockCostType.BYTE
assert costs[0].cost_amount == 1 # 1 credit per 1000 bytes
def test_provider_cost_block(self):
"""Test block that should inherit provider costs."""
# Initially, ProviderCostBlock shouldn't be in BLOCK_COSTS
# (unless it was already registered by another test)
# Register provider costs for the block
register_provider_costs_for_block(ProviderCostBlock)
# Now check if costs were registered from the provider
costs = get_block_costs(ProviderCostBlock)
if costs: # Provider costs should be registered if provider exists
assert len(costs) > 0
assert costs[0].cost_type == BlockCostType.RUN
assert costs[0].cost_amount == 1 # From example_service provider
def test_block_usage_cost_calculation(self):
"""Test actual cost calculation using block_usage_cost."""
# Test fixed cost
block = FixedCostBlock()
cost, filter_used = block_usage_cost(block, {"data": "test"})
assert cost == 5
# Test tiered cost - basic tier
block = TieredCostBlock()
cost, filter_used = block_usage_cost(block, {"data": "test", "tier": "basic"})
assert cost == 1
# Test tiered cost - premium tier
cost, filter_used = block_usage_cost(block, {"data": "test", "tier": "premium"})
assert cost == 10
# Test data-based cost (10KB of data)
block = DataBasedCostBlock()
cost, filter_used = block_usage_cost(
block, {"data": "test", "process_intensive": False}, data_size=10000 # 10KB
)
assert cost == 10000 # 10KB * 1 credit per byte = 10000 credits
def test_cost_decorator_overrides_provider(self):
"""Test that @cost decorator overrides provider costs."""
# Create a test block with both provider and decorator costs
from backend.blocks.examples._config import example_service
from backend.sdk import (
Block,
BlockCategory,
BlockOutput,
BlockSchema,
CredentialsMetaInput,
SchemaField,
cost,
)
@cost(BlockCost(cost_type=BlockCostType.RUN, cost_amount=100))
class TestOverrideBlock(Block):
class Input(BlockSchema):
credentials: CredentialsMetaInput = example_service.credentials_field(
description="Test credentials"
)
data: str = SchemaField(description="Data")
class Output(BlockSchema):
result: str = SchemaField(description="Result")
def __init__(self):
super().__init__(
id="test-override-block-12345678-1234-1234-1234",
description="Test block",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=self.Input,
output_schema=self.Output,
)
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
yield "result", "test"
# The decorator cost should override provider cost
_ = TestOverrideBlock() # Instantiate to register costs
assert TestOverrideBlock in BLOCK_COSTS
costs = BLOCK_COSTS[TestOverrideBlock]
assert len(costs) == 1
assert costs[0].cost_amount == 100 # Decorator cost, not provider's 1
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -59,9 +59,11 @@ class TestAutoRegistry:
class TestOAuthHandler(BaseOAuthHandler):
PROVIDER_NAME = ProviderName.GITHUB
from backend.sdk.provider import OAuthConfig
provider = Provider(
name="oauth_provider",
oauth_handler=TestOAuthHandler,
oauth_config=OAuthConfig(oauth_handler=TestOAuthHandler),
webhook_manager=None,
default_credentials=[],
base_costs=[],
@@ -166,9 +168,11 @@ class TestAutoRegistry:
class TestOAuth2(BaseOAuthHandler):
PROVIDER_NAME = ProviderName.GOOGLE
from backend.sdk.provider import OAuthConfig
provider1 = Provider(
name="provider1",
oauth_handler=TestOAuth1,
oauth_config=OAuthConfig(oauth_handler=TestOAuth1),
webhook_manager=None,
default_credentials=[],
base_costs=[],
@@ -177,7 +181,7 @@ class TestAutoRegistry:
provider2 = Provider(
name="provider2",
oauth_handler=TestOAuth2,
oauth_config=OAuthConfig(oauth_handler=TestOAuth2),
webhook_manager=None,
default_credentials=[],
base_costs=[],
@@ -318,7 +322,8 @@ class TestProviderBuilder:
.build()
)
assert provider.oauth_handler == TestOAuth
assert provider.oauth_config is not None
assert provider.oauth_config.oauth_handler == TestOAuth
assert "oauth2" in provider.supported_auth_types
def test_provider_builder_with_webhook(self):
@@ -406,7 +411,8 @@ class TestProviderBuilder:
assert provider.name == "complete_test"
assert "api_key" in provider.supported_auth_types
assert "oauth2" in provider.supported_auth_types
assert provider.oauth_handler == TestOAuth
assert provider.oauth_config is not None
assert provider.oauth_config.oauth_handler == TestOAuth
assert provider.webhook_manager == TestWebhook
assert len(provider.base_costs) == 1
assert provider._api_client_factory == client_factory

View File

@@ -26,9 +26,9 @@
"defaults"
],
"dependencies": {
"@faker-js/faker": "9.8.0",
"@faker-js/faker": "9.9.0",
"@hookform/resolvers": "5.1.1",
"@next/third-parties": "15.3.3",
"@next/third-parties": "15.3.5",
"@phosphor-icons/react": "2.1.10",
"@radix-ui/react-alert-dialog": "1.1.14",
"@radix-ui/react-avatar": "1.1.10",
@@ -49,13 +49,13 @@
"@radix-ui/react-tabs": "1.1.12",
"@radix-ui/react-toast": "1.2.14",
"@radix-ui/react-tooltip": "1.2.7",
"@sentry/nextjs": "9.27.0",
"@sentry/nextjs": "9.35.0",
"@supabase/ssr": "0.6.1",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.7",
"@supabase/supabase-js": "2.50.3",
"@tanstack/react-query": "5.81.5",
"@tanstack/react-table": "8.21.3",
"@types/jaro-winkler": "0.2.4",
"@xyflow/react": "12.6.4",
"@xyflow/react": "12.8.1",
"ajv": "8.17.1",
"boring-avatars": "1.11.2",
"class-variance-authority": "0.7.1",
@@ -66,21 +66,21 @@
"dotenv": "16.5.0",
"elliptic": "6.6.1",
"embla-carousel-react": "8.6.0",
"framer-motion": "12.16.0",
"framer-motion": "12.23.0",
"geist": "1.4.2",
"jaro-winkler": "0.2.8",
"launchdarkly-react-client-sdk": "3.8.1",
"lodash": "4.17.21",
"lucide-react": "0.513.0",
"lucide-react": "0.525.0",
"moment": "2.30.1",
"next": "15.3.3",
"next": "15.3.5",
"next-themes": "0.4.6",
"party-js": "2.2.0",
"react": "18.3.1",
"react-day-picker": "9.7.0",
"react-day-picker": "9.8.0",
"react-dom": "18.3.1",
"react-drag-drop-files": "2.4.0",
"react-hook-form": "7.57.0",
"react-hook-form": "7.60.0",
"react-icons": "5.5.0",
"react-markdown": "9.0.3",
"react-modal": "3.16.3",
@@ -91,20 +91,20 @@
"tailwindcss-animate": "1.0.7",
"uuid": "11.1.0",
"vaul": "1.1.2",
"zod": "3.25.56"
"zod": "3.25.76"
},
"devDependencies": {
"@chromatic-com/storybook": "4.0.1",
"@playwright/test": "1.53.1",
"@storybook/addon-a11y": "9.0.14",
"@storybook/addon-docs": "9.0.14",
"@storybook/addon-links": "9.0.14",
"@storybook/addon-onboarding": "9.0.14",
"@storybook/nextjs": "9.0.14",
"@playwright/test": "1.53.2",
"@storybook/addon-a11y": "9.0.16",
"@storybook/addon-docs": "9.0.16",
"@storybook/addon-links": "9.0.16",
"@storybook/addon-onboarding": "9.0.16",
"@storybook/nextjs": "9.0.16",
"@tanstack/eslint-plugin-query": "5.81.2",
"@tanstack/react-query-devtools": "5.81.5",
"@types/canvas-confetti": "1.9.0",
"@types/lodash": "4.17.19",
"@types/lodash": "4.17.20",
"@types/negotiator": "0.6.4",
"@types/node": "22.15.30",
"@types/react": "18.3.17",
@@ -115,10 +115,10 @@
"concurrently": "9.2.0",
"cross-env": "7.0.3",
"eslint": "8.57.1",
"eslint-config-next": "15.3.4",
"eslint-plugin-storybook": "9.0.14",
"eslint-config-next": "15.3.5",
"eslint-plugin-storybook": "9.0.16",
"import-in-the-middle": "1.14.2",
"msw": "2.10.2",
"msw": "2.10.3",
"msw-storybook-addon": "2.0.5",
"orval": "7.10.0",
"pbkdf2": "3.1.3",
@@ -126,7 +126,7 @@
"prettier": "3.6.2",
"prettier-plugin-tailwindcss": "0.6.13",
"require-in-the-middle": "7.5.2",
"storybook": "9.0.14",
"storybook": "9.0.16",
"tailwindcss": "3.4.17",
"typescript": "5.8.3"
},

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,13 @@
"use client";
import * as React from "react";
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
import { AgentTableCard } from "./AgentTableCard";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
import { AgentTableCard } from "../AgentTableCard/AgentTableCard";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { useAgentTable } from "./useAgentTable";
import {
AgentTableRow,
AgentTableRowProps,
} from "../AgentTableRow/AgentTableRow";
export interface AgentTableProps {
agents: Omit<
@@ -22,22 +26,9 @@ export const AgentTable: React.FC<AgentTableProps> = ({
onEditSubmission,
onDeleteSubmission,
}) => {
// Use state to track selected agents
const [selectedAgents, setSelectedAgents] = React.useState<Set<string>>(
new Set(),
);
// Handle select all checkbox
const handleSelectAll = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedAgents(new Set(agents.map((agent) => agent.agent_id)));
} else {
setSelectedAgents(new Set());
}
},
[agents],
);
const { selectedAgents, handleSelectAll, setSelectedAgents } = useAgentTable({
agents,
});
return (
<div className="w-full">

View File

@@ -0,0 +1,25 @@
import { useState } from "react";
import { AgentTableRowProps } from "../AgentTableRow/AgentTableRow";
interface useAgentTableProps {
agents: Omit<
AgentTableRowProps,
| "setSelectedAgents"
| "selectedAgents"
| "onEditSubmission"
| "onDeleteSubmission"
>[];
}
export const useAgentTable = ({ agents }: useAgentTableProps) => {
const [selectedAgents, setSelectedAgents] = useState<Set<string>>(new Set());
const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedAgents(new Set(agents.map((agent) => agent.agent_id)));
} else {
setSelectedAgents(new Set());
}
};
return { selectedAgents, handleSelectAll, setSelectedAgents };
};

View File

@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
import Image from "next/image";
import { IconStarFilled, IconMore } from "@/components/ui/icons";
import { Status, StatusType } from "./Status";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { Status, StatusType } from "@/components/agptui/Status";
export interface AgentTableCardProps {
agent_id: string;
@@ -21,7 +20,7 @@ export interface AgentTableCardProps {
onEditSubmission: (submission: StoreSubmissionRequest) => void;
}
export const AgentTableCard: React.FC<AgentTableCardProps> = ({
export const AgentTableCard = ({
agent_id,
agent_version,
agentName,
@@ -33,9 +32,8 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({
runs,
rating,
onEditSubmission,
}) => {
}: AgentTableCardProps) => {
const onEdit = () => {
console.log("Edit agent", agentName);
onEditSubmission({
agent_id,
agent_version,

View File

@@ -1,12 +1,12 @@
"use client";
import * as React from "react";
import Image from "next/image";
import { IconStarFilled, IconMore, IconEdit } from "@/components/ui/icons";
import { Status, StatusType } from "./Status";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { TrashIcon } from "@radix-ui/react-icons";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
import { Status, StatusType } from "@/components/agptui/Status";
import { useAgentTableRow } from "./useAgentTableRow";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
export interface AgentTableRowProps {
agent_id: string;
@@ -27,7 +27,7 @@ export interface AgentTableRowProps {
onDeleteSubmission: (submission_id: string) => void;
}
export const AgentTableRow: React.FC<AgentTableRowProps> = ({
export const AgentTableRow = ({
agent_id,
agent_version,
agentName,
@@ -43,43 +43,21 @@ export const AgentTableRow: React.FC<AgentTableRowProps> = ({
setSelectedAgents,
onEditSubmission,
onDeleteSubmission,
}) => {
// Create a unique ID for the checkbox
const checkboxId = `agent-${id}-checkbox`;
const handleEdit = React.useCallback(() => {
onEditSubmission({
}: AgentTableRowProps) => {
const { checkboxId, handleEdit, handleDelete, handleCheckboxChange } =
useAgentTableRow({
id,
onEditSubmission,
onDeleteSubmission,
agent_id,
agent_version,
slug: "",
name: agentName,
agentName,
sub_heading,
description,
image_urls: imageSrc,
categories: [],
} satisfies StoreSubmissionRequest);
}, [
agent_id,
agent_version,
agentName,
sub_heading,
description,
imageSrc,
onEditSubmission,
]);
const handleDelete = React.useCallback(() => {
onDeleteSubmission(agent_id);
}, [agent_id, onDeleteSubmission]);
const handleCheckboxChange = React.useCallback(() => {
if (selectedAgents.has(agent_id)) {
selectedAgents.delete(agent_id);
} else {
selectedAgents.add(agent_id);
}
setSelectedAgents(new Set(selectedAgents));
}, [agent_id, selectedAgents, setSelectedAgents]);
imageSrc,
selectedAgents,
setSelectedAgents,
});
return (
<div className="hidden items-center border-b border-neutral-300 px-4 py-4 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 md:flex">

View File

@@ -0,0 +1,59 @@
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
interface useAgentTableRowProps {
id: number;
onEditSubmission: (submission: StoreSubmissionRequest) => void;
onDeleteSubmission: (submission_id: string) => void;
agent_id: string;
agent_version: number;
agentName: string;
sub_heading: string;
description: string;
imageSrc: string[];
selectedAgents: Set<string>;
setSelectedAgents: React.Dispatch<React.SetStateAction<Set<string>>>;
}
export const useAgentTableRow = ({
id,
onEditSubmission,
onDeleteSubmission,
agent_id,
agent_version,
agentName,
sub_heading,
description,
imageSrc,
selectedAgents,
setSelectedAgents,
}: useAgentTableRowProps) => {
const checkboxId = `agent-${id}-checkbox`;
const handleEdit = () => {
onEditSubmission({
agent_id,
agent_version,
slug: "",
name: agentName,
sub_heading,
description,
image_urls: imageSrc,
categories: [],
} satisfies StoreSubmissionRequest);
};
const handleDelete = () => {
onDeleteSubmission(agent_id);
};
const handleCheckboxChange = () => {
if (selectedAgents.has(agent_id)) {
selectedAgents.delete(agent_id);
} else {
selectedAgents.add(agent_id);
}
setSelectedAgents(new Set(selectedAgents));
};
return { checkboxId, handleEdit, handleDelete, handleCheckboxChange };
};

View File

@@ -0,0 +1,91 @@
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
import { Button } from "@/components/ui/button";
import { useMainDashboardPage } from "./useMainDashboardPage";
import { Separator } from "@/components/ui/separator";
import { AgentTable } from "../AgentTable/AgentTable";
import { StatusType } from "@/components/agptui/Status";
export const MainDashboardPage = () => {
const {
onOpenPopout,
onDeleteSubmission,
onEditSubmission,
submissions,
isLoading,
openPopout,
submissionData,
popoutStep,
} = useMainDashboardPage();
if (isLoading) {
return "Loading....";
}
return (
<main className="flex-1 py-8">
{/* Header Section */}
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-6">
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
Agent dashboard
</h1>
<div className="space-y-2">
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
Submit a New Agent
</h2>
<p className="text-sm text-[#707070] dark:text-neutral-400">
Select from the list of agents you currently have, or upload from
your local machine.
</p>
</div>
</div>
<PublishAgentPopout
trigger={
<Button
onClick={onOpenPopout}
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
>
Submit agent
</Button>
}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
</div>
<Separator className="mb-8" />
{/* Agents Section */}
<div>
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
/>
)}
</div>
</main>
);
};

View File

@@ -0,0 +1,72 @@
import {
getGetV2ListMySubmissionsQueryKey,
useDeleteV2DeleteStoreSubmission,
useGetV2ListMySubmissions,
} from "@/app/api/__generated__/endpoints/store/store";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { StoreSubmissionsResponse } from "@/app/api/__generated__/models/storeSubmissionsResponse";
import { getQueryClient } from "@/lib/react-query/queryClient";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useState } from "react";
export const useMainDashboardPage = () => {
const queryClient = getQueryClient();
const { user } = useSupabase();
const [openPopout, setOpenPopout] = useState<boolean>(false);
const [submissionData, setSubmissionData] =
useState<StoreSubmissionRequest>();
const [popoutStep, setPopoutStep] = useState<"select" | "info" | "review">(
"info",
);
const { mutateAsync: deleteSubmission } = useDeleteV2DeleteStoreSubmission({
mutation: {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getGetV2ListMySubmissionsQueryKey(),
});
},
},
});
const { data: submissions, isLoading } = useGetV2ListMySubmissions(
undefined,
{
query: {
select: (x) => {
return x.data as StoreSubmissionsResponse;
},
enabled: !!user,
},
},
);
const onEditSubmission = (submission: StoreSubmissionRequest) => {
setSubmissionData(submission);
setPopoutStep("review");
setOpenPopout(true);
};
const onDeleteSubmission = async (submission_id: string) => {
await deleteSubmission({
submissionId: submission_id,
});
};
const onOpenPopout = () => {
setPopoutStep("select");
setOpenPopout(true);
};
return {
onOpenPopout,
onDeleteSubmission,
onEditSubmission,
submissions,
isLoading,
openPopout,
submissionData,
popoutStep,
};
};

View File

@@ -1,133 +1,7 @@
"use client";
import * as React from "react";
import { AgentTable } from "@/components/agptui/AgentTable";
import { Button } from "@/components/agptui/Button";
import { Separator } from "@/components/ui/separator";
import { StatusType } from "@/components/agptui/Status";
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
import { useCallback, useEffect, useState } from "react";
import {
StoreSubmissionsResponse,
StoreSubmissionRequest,
} from "@/lib/autogpt-server-api/types";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { MainDashboardPage } from "./components/MainDashboardPage/MainDashboardPage";
export default function Page() {
const { supabase } = useSupabase();
const api = useBackendAPI();
const [submissions, setSubmissions] = useState<StoreSubmissionsResponse>();
const [openPopout, setOpenPopout] = useState<boolean>(false);
const [submissionData, setSubmissionData] =
useState<StoreSubmissionRequest>();
const [popoutStep, setPopoutStep] = useState<"select" | "info" | "review">(
"info",
);
const fetchData = useCallback(async () => {
try {
const submissions = await api.getStoreSubmissions();
setSubmissions(submissions);
} catch (error) {
console.error("Error fetching submissions:", error);
}
}, [api]);
useEffect(() => {
if (!supabase) {
return;
}
fetchData();
}, [supabase, fetchData]);
const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => {
setSubmissionData(submission);
setPopoutStep("review");
setOpenPopout(true);
}, []);
const onDeleteSubmission = useCallback(
(submission_id: string) => {
if (!supabase) {
return;
}
api.deleteStoreSubmission(submission_id);
fetchData();
},
[api, supabase, fetchData],
);
const onOpenPopout = useCallback(() => {
setPopoutStep("select");
setOpenPopout(true);
}, []);
return (
<main className="flex-1 py-8">
{/* Header Section */}
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-6">
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
Agent dashboard
</h1>
<div className="space-y-2">
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
Submit a New Agent
</h2>
<p className="text-sm text-[#707070] dark:text-neutral-400">
Select from the list of agents you currently have, or upload from
your local machine.
</p>
</div>
</div>
<PublishAgentPopout
trigger={
<Button
onClick={onOpenPopout}
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
>
Submit agent
</Button>
}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
</div>
<Separator className="mb-8" />
{/* Agents Section */}
<div>
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
/>
)}
</div>
</main>
);
return <MainDashboardPage />;
}

View File

@@ -3373,48 +3373,6 @@
}
}
},
"/api/library/agents/by-graph/{graph_id}": {
"get": {
"tags": ["v2", "library", "private"],
"summary": "Get Library Agent By Graph Id",
"operationId": "getV2GetLibraryAgentByGraphId",
"parameters": [
{
"name": "graph_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Graph Id" }
},
{
"name": "version",
"in": "query",
"required": false,
"schema": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
"title": "Version"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LibraryAgent" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/api/library/agents/marketplace/{store_listing_version_id}": {
"get": {
"tags": ["v2", "library", "private", "store, library"],
@@ -5330,7 +5288,6 @@
"AGENT_INPUT",
"CONGRATS",
"GET_RESULTS",
"RUN_AGENTS",
"MARKETPLACE_VISIT",
"MARKETPLACE_ADD_AGENT",
"MARKETPLACE_RUN_AGENT",

View File

@@ -14,13 +14,11 @@ import {
} from "../PublishAgentSelectInfo";
import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview";
import { Button } from "../Button";
import {
StoreSubmissionRequest,
MyAgentsResponse,
} from "@/lib/autogpt-server-api";
import { MyAgentsResponse } from "@/lib/autogpt-server-api";
import { useRouter } from "next/navigation";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useToast } from "@/components/ui/use-toast";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
interface PublishAgentPopoutProps {
trigger?: React.ReactNode;
openPopout?: boolean;
@@ -263,8 +261,8 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
<PublishAgentAwaitingReview
agentName={publishData.name}
subheader={publishData.sub_heading}
description={publishData.description}
thumbnailSrc={publishData.image_urls[0]}
description={publishData.description || ""}
thumbnailSrc={publishData.image_urls?.[0]}
onClose={handleClose}
onDone={handleClose}
onViewProgress={() => {