mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-21 04:57:58 -05:00
Compare commits
33 Commits
testing-cl
...
swiftyos/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cd26bba0e | ||
|
|
95bc7dbfca | ||
|
|
ff6ad3270e | ||
|
|
9ab5dd597b | ||
|
|
324216df31 | ||
|
|
3408277c0e | ||
|
|
83d96eb4b5 | ||
|
|
c26a96fc6d | ||
|
|
1aae4e7474 | ||
|
|
5981c4d70b | ||
|
|
8ecadca8a9 | ||
|
|
a7d7192022 | ||
|
|
95ce2c825f | ||
|
|
b9366c9b28 | ||
|
|
79948263c8 | ||
|
|
57223e6343 | ||
|
|
211e53bf5d | ||
|
|
8aba4a5d48 | ||
|
|
6ba9fd9cb4 | ||
|
|
b16c2eed52 | ||
|
|
5af718c9f5 | ||
|
|
a588cf1dc5 | ||
|
|
98a1adc397 | ||
|
|
19728ebc05 | ||
|
|
3e117aac5d | ||
|
|
8dabe6c70d | ||
|
|
a04919beca | ||
|
|
c6b22842a4 | ||
|
|
614f751a90 | ||
|
|
c458bec9c7 | ||
|
|
040bde3f49 | ||
|
|
71028d57d7 | ||
|
|
dbf014f936 |
@@ -173,6 +173,9 @@ EXA_API_KEY=
|
|||||||
# E2B
|
# E2B
|
||||||
E2B_API_KEY=
|
E2B_API_KEY=
|
||||||
|
|
||||||
|
# Example API Key
|
||||||
|
EXAMPLE_API_KEY=
|
||||||
|
|
||||||
# Mem0
|
# Mem0
|
||||||
MEM0_API_KEY=
|
MEM0_API_KEY=
|
||||||
|
|
||||||
|
|||||||
137
autogpt_platform/backend/backend/blocks/example/_api.py
Normal file
137
autogpt_platform/backend/backend/blocks/example/_api.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
API module for Example API integration.
|
||||||
|
|
||||||
|
This module provides a example of how to create a client for an API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We also have a Json Wrapper library available in backend.util.json
|
||||||
|
from json import JSONDecodeError
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from backend.data.model import APIKeyCredentials
|
||||||
|
|
||||||
|
# This is a wrapper around the requests library that is used to make API requests.
|
||||||
|
from backend.util.request import Requests
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleAPIException(Exception):
|
||||||
|
def __init__(self, message: str, status_code: int):
|
||||||
|
super().__init__(message)
|
||||||
|
self.status_code = status_code
|
||||||
|
|
||||||
|
|
||||||
|
class CreateResourceResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
is_funny: bool
|
||||||
|
|
||||||
|
|
||||||
|
class GetResourceResponse(BaseModel):
|
||||||
|
message: str
|
||||||
|
is_funny: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleClient:
|
||||||
|
"""Client for the Example API"""
|
||||||
|
|
||||||
|
API_BASE_URL = "https://api.example.com/v1"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
credentials: Optional[APIKeyCredentials] = None,
|
||||||
|
custom_requests: Optional[Requests] = None,
|
||||||
|
):
|
||||||
|
if custom_requests:
|
||||||
|
self._requests = custom_requests
|
||||||
|
else:
|
||||||
|
headers: dict[str, str] = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
if credentials:
|
||||||
|
headers["Authorization"] = credentials.auth_header()
|
||||||
|
|
||||||
|
self._requests = Requests(
|
||||||
|
extra_headers=headers,
|
||||||
|
raise_for_status=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _handle_response(response) -> Any:
|
||||||
|
"""
|
||||||
|
Handles API response and checks for errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
response: The response object from the request.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The parsed JSON response data.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExampleAPIException: If the API request fails.
|
||||||
|
"""
|
||||||
|
if not response.ok:
|
||||||
|
try:
|
||||||
|
error_data = response.json()
|
||||||
|
error_message = error_data.get("error", {}).get("message", "")
|
||||||
|
except JSONDecodeError:
|
||||||
|
error_message = response.text
|
||||||
|
|
||||||
|
raise ExampleAPIException(
|
||||||
|
f"Example API request failed ({response.status_code}): {error_message}",
|
||||||
|
response.status_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
if "errors" in response_data:
|
||||||
|
# This is an example error and needs to be
|
||||||
|
# replaced with how the real API returns errors
|
||||||
|
error_messages = [
|
||||||
|
error.get("message", "") for error in response_data["errors"]
|
||||||
|
]
|
||||||
|
raise ExampleAPIException(
|
||||||
|
f"Example API returned errors: {', '.join(error_messages)}",
|
||||||
|
response.status_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response_data
|
||||||
|
|
||||||
|
def get_resource(self, resource_id: str) -> GetResourceResponse:
|
||||||
|
"""
|
||||||
|
Fetches a resource from the Example API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource_id: The ID of the resource to fetch.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The resource data as a GetResourceResponse object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExampleAPIException: If the API request fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self._requests.get(
|
||||||
|
f"{self.API_BASE_URL}/resources/{resource_id}"
|
||||||
|
)
|
||||||
|
return GetResourceResponse(**self._handle_response(response))
|
||||||
|
except Exception as e:
|
||||||
|
raise ExampleAPIException(f"Failed to get resource: {str(e)}", 500)
|
||||||
|
|
||||||
|
def create_resource(self, data: dict) -> CreateResourceResponse:
|
||||||
|
"""
|
||||||
|
Creates a new resource via the Example API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The resource data to create.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The created resource data as a CreateResourceResponse object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExampleAPIException: If the API request fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = self._requests.post(f"{self.API_BASE_URL}/resources", json=data)
|
||||||
|
return CreateResourceResponse(**self._handle_response(response))
|
||||||
|
except Exception as e:
|
||||||
|
raise ExampleAPIException(f"Failed to create resource: {str(e)}", 500)
|
||||||
37
autogpt_platform/backend/backend/blocks/example/_auth.py
Normal file
37
autogpt_platform/backend/backend/blocks/example/_auth.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
Authentication module for Example API integration.
|
||||||
|
|
||||||
|
This module provides credential types and test credentials for the Example API integration.
|
||||||
|
It defines the structure for API key credentials used to authenticate with the Example API
|
||||||
|
and provides mock credentials for testing purposes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from pydantic import SecretStr
|
||||||
|
|
||||||
|
from backend.data.model import APIKeyCredentials, CredentialsMetaInput
|
||||||
|
from backend.integrations.providers import ProviderName
|
||||||
|
|
||||||
|
# Define the type of credentials input expected for Example API
|
||||||
|
ExampleCredentialsInput = CredentialsMetaInput[
|
||||||
|
Literal[ProviderName.EXAMPLE_PROVIDER], Literal["api_key"]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Mock credentials for testing Example API integration
|
||||||
|
TEST_CREDENTIALS = APIKeyCredentials(
|
||||||
|
id="9191c4f0-498f-4235-a79c-59c0e37454d4",
|
||||||
|
provider="example-provider",
|
||||||
|
api_key=SecretStr("mock-example-api-key"),
|
||||||
|
title="Mock Example API key",
|
||||||
|
expires_at=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Dictionary representation of test credentials for input fields
|
||||||
|
TEST_CREDENTIALS_INPUT = {
|
||||||
|
"provider": TEST_CREDENTIALS.provider,
|
||||||
|
"id": TEST_CREDENTIALS.id,
|
||||||
|
"type": TEST_CREDENTIALS.type,
|
||||||
|
"title": TEST_CREDENTIALS.title,
|
||||||
|
}
|
||||||
154
autogpt_platform/backend/backend/blocks/example/example.py
Normal file
154
autogpt_platform/backend/backend/blocks/example/example.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||||
|
from backend.data.model import APIKeyCredentials, CredentialsField, SchemaField
|
||||||
|
|
||||||
|
from ._api import ExampleClient
|
||||||
|
from ._auth import TEST_CREDENTIALS, TEST_CREDENTIALS_INPUT, ExampleCredentialsInput
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GreetingMessage(BaseModel):
|
||||||
|
message: str
|
||||||
|
is_funny: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleBlock(Block):
|
||||||
|
|
||||||
|
class Input(BlockSchema):
|
||||||
|
name: str = SchemaField(
|
||||||
|
description="The name of the example block", placeholder="Enter a name"
|
||||||
|
)
|
||||||
|
greetings: list[str] = SchemaField(
|
||||||
|
description="The greetings to display", default=["Hello", "Hi", "Hey"]
|
||||||
|
)
|
||||||
|
is_funny: bool = SchemaField(
|
||||||
|
description="Whether the block is funny",
|
||||||
|
placeholder="True",
|
||||||
|
default=True,
|
||||||
|
# Advanced fields are moved to the "Advanced" dropdown in the UI
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
greeting_context: str = SchemaField(
|
||||||
|
description="The context of the greeting",
|
||||||
|
placeholder="Enter a context",
|
||||||
|
default="The user is looking for an inspirational greeting",
|
||||||
|
# Hidden fields are not shown in the UI at all
|
||||||
|
hidden=True,
|
||||||
|
)
|
||||||
|
# Only if the block needs credentials
|
||||||
|
credentials: ExampleCredentialsInput = CredentialsField(
|
||||||
|
description="The credentials for the example block"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Output(BlockSchema):
|
||||||
|
response: GreetingMessage = SchemaField(
|
||||||
|
description="The response object generated by the example block."
|
||||||
|
)
|
||||||
|
all_responses: list[GreetingMessage] = SchemaField(
|
||||||
|
description="All the responses from the example block."
|
||||||
|
)
|
||||||
|
greeting_count: int = SchemaField(
|
||||||
|
description="The number of greetings in the input."
|
||||||
|
)
|
||||||
|
error: str = SchemaField(description="The error from the example block")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
# The unique identifier for the block, this value will be persisted in the DB.
|
||||||
|
# It should be unique and constant across the application run.
|
||||||
|
# Use the UUID format for the ID.
|
||||||
|
id="380694d5-3b2e-4130-bced-b43752b70de9",
|
||||||
|
# The description of the block, explaining what the block does.
|
||||||
|
description="The example block",
|
||||||
|
# The set of categories that the block belongs to.
|
||||||
|
# Each category is an instance of BlockCategory Enum.
|
||||||
|
categories={BlockCategory.BASIC},
|
||||||
|
# The schema, defined as a Pydantic model, for the input data.
|
||||||
|
input_schema=ExampleBlock.Input,
|
||||||
|
# The schema, defined as a Pydantic model, for the output data.
|
||||||
|
output_schema=ExampleBlock.Output,
|
||||||
|
# The list or single sample input data for the block, for testing.
|
||||||
|
# This is an instance of the Input schema with sample values.
|
||||||
|
test_input={
|
||||||
|
"name": "Craig",
|
||||||
|
"greetings": ["Hello", "Hi", "Hey"],
|
||||||
|
"is_funny": True,
|
||||||
|
"credentials": TEST_CREDENTIALS_INPUT,
|
||||||
|
},
|
||||||
|
# The list or single expected output if the test_input is run.
|
||||||
|
# Each output is a tuple of (output_name, output_data).
|
||||||
|
test_output=[
|
||||||
|
("response", GreetingMessage(message="Hello, world!", is_funny=True)),
|
||||||
|
(
|
||||||
|
"response",
|
||||||
|
GreetingMessage(message="Hello, world!", is_funny=True),
|
||||||
|
), # We mock the function
|
||||||
|
(
|
||||||
|
"response",
|
||||||
|
GreetingMessage(message="Hello, world!", is_funny=True),
|
||||||
|
), # We mock the function
|
||||||
|
(
|
||||||
|
"all_responses",
|
||||||
|
[
|
||||||
|
GreetingMessage(message="Hello, world!", is_funny=True),
|
||||||
|
GreetingMessage(message="Hello, world!", is_funny=True),
|
||||||
|
GreetingMessage(message="Hello, world!", is_funny=True),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("greeting_count", 3),
|
||||||
|
],
|
||||||
|
# Function names on the block implementation to mock on test run.
|
||||||
|
# Each mock is a dictionary with function names as keys and mock implementations as values.
|
||||||
|
test_mock={
|
||||||
|
"my_function_that_can_be_mocked": lambda *args, **kwargs: GreetingMessage(
|
||||||
|
message="Hello, world!", is_funny=True
|
||||||
|
)
|
||||||
|
},
|
||||||
|
# The credentials required for testing the block.
|
||||||
|
# This is an instance of APIKeyCredentials with sample values.
|
||||||
|
test_credentials=TEST_CREDENTIALS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def my_function_that_can_be_mocked(
|
||||||
|
self, name: str, credentials: APIKeyCredentials
|
||||||
|
) -> GreetingMessage:
|
||||||
|
logger.info("my_function_that_can_be_mocked called with input: %s", name)
|
||||||
|
|
||||||
|
# Use the ExampleClient from _api.py to make an API call
|
||||||
|
client = ExampleClient(credentials=credentials)
|
||||||
|
|
||||||
|
# Create a sample resource using the client
|
||||||
|
resource_data = {"name": name, "type": "greeting"}
|
||||||
|
# If your API response object matches the return type of the function,
|
||||||
|
# there is no need to convert the object. In this case we have a different
|
||||||
|
# object type for the response and the return type of the function.
|
||||||
|
return GreetingMessage(**client.create_resource(resource_data).model_dump())
|
||||||
|
|
||||||
|
def run(
|
||||||
|
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||||
|
) -> BlockOutput:
|
||||||
|
"""
|
||||||
|
The run function implements the block's core logic. It processes the input_data
|
||||||
|
and yields the block's output.
|
||||||
|
|
||||||
|
In addition to credentials, the following parameters can be specified:
|
||||||
|
graph_id: The ID of the graph containing this block.
|
||||||
|
node_id: The ID of this block's node in the graph.
|
||||||
|
graph_exec_id: The ID of the current graph execution.
|
||||||
|
node_exec_id: The ID of the current node execution.
|
||||||
|
user_id: The ID of the user executing the block.
|
||||||
|
"""
|
||||||
|
rtn_all_responses: list[GreetingMessage] = []
|
||||||
|
# Here we deomonstrate best practice for blocks that need to yield multiple items.
|
||||||
|
# We yield each item from the list to allow for operations on each element.
|
||||||
|
# We also yield the complete list for situations when the full list is needed.
|
||||||
|
for greeting in input_data.greetings:
|
||||||
|
message = self.my_function_that_can_be_mocked(greeting, credentials)
|
||||||
|
rtn_all_responses.append(message)
|
||||||
|
yield "response", message
|
||||||
|
yield "all_responses", rtn_all_responses
|
||||||
|
yield "greeting_count", len(input_data.greetings)
|
||||||
65
autogpt_platform/backend/backend/blocks/example/triggers.py
Normal file
65
autogpt_platform/backend/backend/blocks/example/triggers.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from backend.data.block import (
|
||||||
|
Block,
|
||||||
|
BlockCategory,
|
||||||
|
BlockManualWebhookConfig,
|
||||||
|
BlockOutput,
|
||||||
|
BlockSchema,
|
||||||
|
)
|
||||||
|
from backend.data.model import SchemaField
|
||||||
|
from backend.integrations.webhooks.example import ExampleWebhookEventType
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleTriggerBlock(Block):
|
||||||
|
"""
|
||||||
|
A trigger block that is activated by an external webhook event.
|
||||||
|
|
||||||
|
Unlike standard blocks that are manually executed, trigger blocks are automatically
|
||||||
|
activated when a webhook event is received from the specified provider.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Input(BlockSchema):
|
||||||
|
# The payload field is hidden because it's automatically populated by the webhook
|
||||||
|
# system rather than being manually entered by the user
|
||||||
|
payload: dict = SchemaField(hidden=True)
|
||||||
|
|
||||||
|
class Output(BlockSchema):
|
||||||
|
event_data: dict = SchemaField(
|
||||||
|
description="The contents of the example webhook event."
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
id="7c5933ce-d60c-42dd-9c4e-db82496474a3",
|
||||||
|
description="This block will output the contents of an example webhook event.",
|
||||||
|
categories={BlockCategory.BASIC},
|
||||||
|
input_schema=ExampleTriggerBlock.Input,
|
||||||
|
output_schema=ExampleTriggerBlock.Output,
|
||||||
|
# The webhook_config is a key difference from standard blocks
|
||||||
|
# It defines which external service can trigger this block and what type of events it responds to
|
||||||
|
webhook_config=BlockManualWebhookConfig(
|
||||||
|
provider="example_provider", # The external service that will send webhook events
|
||||||
|
webhook_type=ExampleWebhookEventType.EXAMPLE_EVENT, # The specific event type this block responds to
|
||||||
|
),
|
||||||
|
# Test input for trigger blocks should mimic the payload structure that would be received from the webhook
|
||||||
|
test_input=[
|
||||||
|
{
|
||||||
|
"payload": {
|
||||||
|
"event_type": "example",
|
||||||
|
"data": "Sample webhook data",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
test_output=[
|
||||||
|
("event_data", {"event_type": "example", "data": "Sample webhook data"})
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||||
|
# For trigger blocks, the run method is called automatically when a webhook event is received
|
||||||
|
# The payload from the webhook is passed in as input_data.payload
|
||||||
|
logger.info("Example trigger block run with payload: %s", input_data.payload)
|
||||||
|
yield "event_data", input_data.payload
|
||||||
@@ -2,6 +2,7 @@ from typing import Type
|
|||||||
|
|
||||||
from backend.blocks.ai_music_generator import AIMusicGeneratorBlock
|
from backend.blocks.ai_music_generator import AIMusicGeneratorBlock
|
||||||
from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock
|
from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock
|
||||||
|
from backend.blocks.example.example import ExampleBlock
|
||||||
from backend.blocks.ideogram import IdeogramModelBlock
|
from backend.blocks.ideogram import IdeogramModelBlock
|
||||||
from backend.blocks.jina.embeddings import JinaEmbeddingBlock
|
from backend.blocks.jina.embeddings import JinaEmbeddingBlock
|
||||||
from backend.blocks.jina.search import ExtractWebsiteContentBlock, SearchTheWebBlock
|
from backend.blocks.jina.search import ExtractWebsiteContentBlock, SearchTheWebBlock
|
||||||
@@ -23,6 +24,7 @@ from backend.data.cost import BlockCost, BlockCostType
|
|||||||
from backend.integrations.credentials_store import (
|
from backend.integrations.credentials_store import (
|
||||||
anthropic_credentials,
|
anthropic_credentials,
|
||||||
did_credentials,
|
did_credentials,
|
||||||
|
example_credentials,
|
||||||
groq_credentials,
|
groq_credentials,
|
||||||
ideogram_credentials,
|
ideogram_credentials,
|
||||||
jina_credentials,
|
jina_credentials,
|
||||||
@@ -267,4 +269,16 @@ BLOCK_COSTS: dict[Type[Block], list[BlockCost]] = {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
SmartDecisionMakerBlock: LLM_COST,
|
SmartDecisionMakerBlock: LLM_COST,
|
||||||
|
ExampleBlock: [
|
||||||
|
BlockCost(
|
||||||
|
cost_amount=1,
|
||||||
|
cost_filter={
|
||||||
|
"credentials": {
|
||||||
|
"id": example_credentials.id,
|
||||||
|
"provider": example_credentials.provider,
|
||||||
|
"type": example_credentials.type,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,16 @@ zerobounce_credentials = APIKeyCredentials(
|
|||||||
expires_at=None,
|
expires_at=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
example_credentials = APIKeyCredentials(
|
||||||
|
id="a2b7f68f-aa6a-4995-99ec-b45b40d33498",
|
||||||
|
provider="example-provider",
|
||||||
|
api_key=SecretStr(settings.secrets.example_api_key),
|
||||||
|
title="Use Credits for Example",
|
||||||
|
expires_at=None,
|
||||||
|
)
|
||||||
|
|
||||||
DEFAULT_CREDENTIALS = [
|
DEFAULT_CREDENTIALS = [
|
||||||
|
example_credentials,
|
||||||
ollama_credentials,
|
ollama_credentials,
|
||||||
revid_credentials,
|
revid_credentials,
|
||||||
ideogram_credentials,
|
ideogram_credentials,
|
||||||
@@ -225,6 +234,8 @@ class IntegrationCredentialsStore:
|
|||||||
all_credentials.append(ollama_credentials)
|
all_credentials.append(ollama_credentials)
|
||||||
|
|
||||||
# These will only be added if the API key is set
|
# These will only be added if the API key is set
|
||||||
|
if settings.secrets.example_api_key:
|
||||||
|
all_credentials.append(example_credentials)
|
||||||
if settings.secrets.revid_api_key:
|
if settings.secrets.revid_api_key:
|
||||||
all_credentials.append(revid_credentials)
|
all_credentials.append(revid_credentials)
|
||||||
if settings.secrets.ideogram_api_key:
|
if settings.secrets.ideogram_api_key:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class ProviderName(str, Enum):
|
|||||||
D_ID = "d_id"
|
D_ID = "d_id"
|
||||||
E2B = "e2b"
|
E2B = "e2b"
|
||||||
EXA = "exa"
|
EXA = "exa"
|
||||||
|
EXAMPLE_PROVIDER = "example-provider"
|
||||||
FAL = "fal"
|
FAL = "fal"
|
||||||
GITHUB = "github"
|
GITHUB = "github"
|
||||||
GOOGLE = "google"
|
GOOGLE = "google"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .compass import CompassWebhookManager
|
from .compass import CompassWebhookManager
|
||||||
|
from .example import ExampleWebhookManager
|
||||||
from .github import GithubWebhooksManager
|
from .github import GithubWebhooksManager
|
||||||
from .slant3d import Slant3DWebhooksManager
|
from .slant3d import Slant3DWebhooksManager
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ WEBHOOK_MANAGERS_BY_NAME: dict["ProviderName", type["BaseWebhooksManager"]] = {
|
|||||||
CompassWebhookManager,
|
CompassWebhookManager,
|
||||||
GithubWebhooksManager,
|
GithubWebhooksManager,
|
||||||
Slant3DWebhooksManager,
|
Slant3DWebhooksManager,
|
||||||
|
ExampleWebhookManager,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
# --8<-- [end:WEBHOOK_MANAGERS_BY_NAME]
|
# --8<-- [end:WEBHOOK_MANAGERS_BY_NAME]
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from fastapi import Request
|
||||||
|
from strenum import StrEnum
|
||||||
|
|
||||||
|
from backend.data import integrations
|
||||||
|
from backend.data.model import APIKeyCredentials, Credentials
|
||||||
|
from backend.integrations.providers import ProviderName
|
||||||
|
|
||||||
|
from ._manual_base import ManualWebhookManagerBase
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleWebhookEventType(StrEnum):
|
||||||
|
EXAMPLE_EVENT = "example_event"
|
||||||
|
ANOTHER_EXAMPLE_EVENT = "another_example_event"
|
||||||
|
|
||||||
|
|
||||||
|
# ExampleWebhookManager is a class that manages webhooks for a hypothetical provider.
|
||||||
|
# It extends ManualWebhookManagerBase, which provides base functionality for manual webhook management.
|
||||||
|
class ExampleWebhookManager(ManualWebhookManagerBase):
|
||||||
|
# Define the provider name for this webhook manager.
|
||||||
|
PROVIDER_NAME = ProviderName.EXAMPLE_PROVIDER
|
||||||
|
# Define the types of webhooks this manager can handle.
|
||||||
|
WebhookEventType = ExampleWebhookEventType
|
||||||
|
|
||||||
|
BASE_URL = "https://api.example.com"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def validate_payload(
|
||||||
|
cls, webhook: integrations.Webhook, request: Request
|
||||||
|
) -> tuple[dict, str]:
|
||||||
|
"""
|
||||||
|
Validate the incoming webhook payload.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
webhook (integrations.Webhook): The webhook object.
|
||||||
|
request (Request): The incoming request object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A tuple containing the payload as a dictionary and the event type as a string.
|
||||||
|
"""
|
||||||
|
# Extract the JSON payload from the request.
|
||||||
|
payload = await request.json()
|
||||||
|
# Set the event type based on the webhook type in the payload.
|
||||||
|
event_type = payload.get("webhook_type", ExampleWebhookEventType.EXAMPLE_EVENT)
|
||||||
|
|
||||||
|
# For the payload its better to return a pydantic model
|
||||||
|
# rather than a weakly typed dict here
|
||||||
|
return payload, event_type
|
||||||
|
|
||||||
|
async def _register_webhook(
|
||||||
|
self,
|
||||||
|
credentials: Credentials,
|
||||||
|
webhook_type: str,
|
||||||
|
resource: str,
|
||||||
|
events: list[str],
|
||||||
|
ingress_url: str,
|
||||||
|
secret: str,
|
||||||
|
) -> tuple[str, dict]:
|
||||||
|
"""
|
||||||
|
Register a new webhook with the provider.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
credentials (Credentials): The credentials required for authentication.
|
||||||
|
webhook_type (str): The type of webhook to register.
|
||||||
|
resource (str): The resource associated with the webhook.
|
||||||
|
events (list[str]): The list of events to subscribe to.
|
||||||
|
ingress_url (str): The URL where the webhook will send data.
|
||||||
|
secret (str): A secret for securing the webhook.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A tuple containing the provider's webhook ID, if any, and the webhook configuration as a dictionary.
|
||||||
|
"""
|
||||||
|
# Ensure the credentials are of the correct type.
|
||||||
|
if not isinstance(credentials, APIKeyCredentials):
|
||||||
|
raise ValueError("API key is required to register a webhook")
|
||||||
|
|
||||||
|
# Prepare the headers for the request, including the API key.
|
||||||
|
headers = {
|
||||||
|
"api-key": credentials.api_key.get_secret_value(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare the payload for the request. Note that the events list is not used.
|
||||||
|
# This is just a fake example
|
||||||
|
payload = {"endPoint": ingress_url}
|
||||||
|
|
||||||
|
# Send a POST request to register the webhook.
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.BASE_URL}/example/webhookSubscribe", headers=headers, json=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if the response indicates a failure.
|
||||||
|
if not response.ok:
|
||||||
|
error = response.json().get("error", "Unknown error")
|
||||||
|
raise RuntimeError(f"Failed to register webhook: {error}")
|
||||||
|
|
||||||
|
# Prepare the webhook configuration to return.
|
||||||
|
webhook_config = {
|
||||||
|
"endpoint": ingress_url,
|
||||||
|
"provider": self.PROVIDER_NAME,
|
||||||
|
"events": ["example_event"],
|
||||||
|
"type": webhook_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", webhook_config
|
||||||
|
|
||||||
|
async def _deregister_webhook(
|
||||||
|
self, webhook: integrations.Webhook, credentials: Credentials
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Deregister a webhook with the provider.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
webhook (integrations.Webhook): The webhook object to deregister.
|
||||||
|
credentials (Credentials): The credentials associated with the webhook.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the webhook doesn't belong to the credentials or if deregistration fails.
|
||||||
|
"""
|
||||||
|
if webhook.credentials_id != credentials.id:
|
||||||
|
raise ValueError(
|
||||||
|
f"Webhook #{webhook.id} does not belong to credentials {credentials.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(credentials, APIKeyCredentials):
|
||||||
|
raise ValueError("API key is required to deregister a webhook")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"api-key": credentials.api_key.get_secret_value(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Construct the delete URL based on the webhook information
|
||||||
|
delete_url = f"{self.BASE_URL}/example/webhooks/{webhook.provider_webhook_id}"
|
||||||
|
|
||||||
|
response = requests.delete(delete_url, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code not in [204, 404]:
|
||||||
|
# 204 means successful deletion, 404 means the webhook was already deleted
|
||||||
|
error = response.json().get("error", "Unknown error")
|
||||||
|
raise ValueError(f"Failed to delete webhook: {error}")
|
||||||
|
|
||||||
|
# If we reach here, the webhook was successfully deleted or didn't exist
|
||||||
@@ -404,6 +404,7 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
|||||||
smartlead_api_key: str = Field(default="", description="SmartLead API Key")
|
smartlead_api_key: str = Field(default="", description="SmartLead API Key")
|
||||||
zerobounce_api_key: str = Field(default="", description="ZeroBounce API Key")
|
zerobounce_api_key: str = Field(default="", description="ZeroBounce API Key")
|
||||||
|
|
||||||
|
example_api_key: str = Field(default="", description="Example API Key")
|
||||||
# Add more secret fields as needed
|
# Add more secret fields as needed
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const providerIcons: Record<
|
|||||||
smartlead: fallbackIcon,
|
smartlead: fallbackIcon,
|
||||||
todoist: fallbackIcon,
|
todoist: fallbackIcon,
|
||||||
zerobounce: fallbackIcon,
|
zerobounce: fallbackIcon,
|
||||||
|
example: fallbackIcon,
|
||||||
};
|
};
|
||||||
// --8<-- [end:ProviderIconsEmbed]
|
// --8<-- [end:ProviderIconsEmbed]
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const providerDisplayNames: Record<CredentialsProviderName, string> = {
|
|||||||
todoist: "Todoist",
|
todoist: "Todoist",
|
||||||
unreal_speech: "Unreal Speech",
|
unreal_speech: "Unreal Speech",
|
||||||
zerobounce: "ZeroBounce",
|
zerobounce: "ZeroBounce",
|
||||||
|
example: "Example",
|
||||||
} as const;
|
} as const;
|
||||||
// --8<-- [end:CredentialsProviderNames]
|
// --8<-- [end:CredentialsProviderNames]
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export const PROVIDER_NAMES = {
|
|||||||
UNREAL_SPEECH: "unreal_speech",
|
UNREAL_SPEECH: "unreal_speech",
|
||||||
TODOIST: "todoist",
|
TODOIST: "todoist",
|
||||||
ZEROBOUNCE: "zerobounce",
|
ZEROBOUNCE: "zerobounce",
|
||||||
|
EXAMPLE: "example",
|
||||||
} as const;
|
} as const;
|
||||||
// --8<-- [end:BlockIOCredentialsSubSchema]
|
// --8<-- [end:BlockIOCredentialsSubSchema]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user