mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
merge
This commit is contained in:
48
autogpt_platform/autogpt_libs/poetry.lock
generated
48
autogpt_platform/autogpt_libs/poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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()
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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", ""
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
425
autogpt_platform/backend/poetry.lock
generated
425
autogpt_platform/backend/poetry.lock
generated
@@ -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"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"])
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
2349
autogpt_platform/frontend/pnpm-lock.yaml
generated
2349
autogpt_platform/frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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">
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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">
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
Reference in New Issue
Block a user