mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-13 17:18:08 -05:00
Compare commits
2 Commits
fix/execut
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f818ef157 | ||
|
|
636574a720 |
@@ -9,6 +9,8 @@ from pydantic import BaseModel
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.settings import Settings
|
||||
from backend.util.file import MediaFileType, store_media_file, get_exec_file_path
|
||||
from pathlib import Path
|
||||
|
||||
from ._auth import (
|
||||
GOOGLE_OAUTH_IS_CONFIGURED,
|
||||
@@ -28,6 +30,7 @@ class Attachment(BaseModel):
|
||||
|
||||
|
||||
class Email(BaseModel):
|
||||
threadId: str
|
||||
id: str
|
||||
subject: str
|
||||
snippet: str
|
||||
@@ -82,6 +85,7 @@ class GmailReadBlock(Block):
|
||||
(
|
||||
"email",
|
||||
{
|
||||
"threadId": "t1",
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
@@ -97,6 +101,7 @@ class GmailReadBlock(Block):
|
||||
"emails",
|
||||
[
|
||||
{
|
||||
"threadId": "t1",
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
@@ -113,6 +118,7 @@ class GmailReadBlock(Block):
|
||||
test_mock={
|
||||
"_read_emails": lambda *args, **kwargs: [
|
||||
{
|
||||
"threadId": "t1",
|
||||
"id": "1",
|
||||
"subject": "Test Email",
|
||||
"snippet": "This is a test email",
|
||||
@@ -185,6 +191,7 @@ class GmailReadBlock(Block):
|
||||
attachments = self._get_attachments(service, msg)
|
||||
|
||||
email = Email(
|
||||
threadId=msg["threadId"],
|
||||
id=msg["id"],
|
||||
subject=headers.get("subject", "No Subject"),
|
||||
snippet=msg["snippet"],
|
||||
@@ -528,3 +535,180 @@ class GmailRemoveLabelBlock(Block):
|
||||
if label["name"] == label_name:
|
||||
return label["id"]
|
||||
return None
|
||||
|
||||
|
||||
class GmailGetThreadBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: GoogleCredentialsInput = GoogleCredentialsField(
|
||||
["https://www.googleapis.com/auth/gmail.readonly"]
|
||||
)
|
||||
threadId: str = SchemaField(description="Gmail thread ID")
|
||||
includeSpamTrash: bool = SchemaField(
|
||||
description="Include messages from Spam and Trash", default=False
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
thread: dict = SchemaField(description="Raw Gmail thread resource")
|
||||
error: str = SchemaField(description="Error message if any")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="21a79166-9df7-4b5f-9f36-96f639d86112",
|
||||
description="Get a full Gmail thread by ID",
|
||||
categories={BlockCategory.COMMUNICATION},
|
||||
input_schema=GmailGetThreadBlock.Input,
|
||||
output_schema=GmailGetThreadBlock.Output,
|
||||
disabled=not GOOGLE_OAUTH_IS_CONFIGURED,
|
||||
test_input={"threadId": "t1", "credentials": TEST_CREDENTIALS_INPUT},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[("thread", {"id": "t1"})],
|
||||
test_mock={
|
||||
"_get_thread": lambda *args, **kwargs: {"id": "t1"}
|
||||
},
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: GoogleCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
service = GmailReadBlock._build_service(credentials, **kwargs)
|
||||
thread = self._get_thread(
|
||||
service, input_data.threadId, input_data.includeSpamTrash
|
||||
)
|
||||
yield "thread", thread
|
||||
|
||||
def _get_thread(self, service, thread_id: str, include_spam_trash: bool) -> dict:
|
||||
return (
|
||||
service.users()
|
||||
.threads()
|
||||
.get(
|
||||
userId="me",
|
||||
id=thread_id,
|
||||
format="full",
|
||||
includeSpamTrash=include_spam_trash,
|
||||
)
|
||||
.execute()
|
||||
)
|
||||
|
||||
|
||||
class GmailReplyBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: GoogleCredentialsInput = GoogleCredentialsField(
|
||||
["https://www.googleapis.com/auth/gmail.send"]
|
||||
)
|
||||
threadId: str = SchemaField(description="Thread ID to reply in")
|
||||
parentMessageId: str = SchemaField(
|
||||
description="ID of the message being replied to"
|
||||
)
|
||||
to: list[str] = SchemaField(description="To recipients", default_factory=list)
|
||||
cc: list[str] = SchemaField(description="CC recipients", default_factory=list)
|
||||
bcc: list[str] = SchemaField(description="BCC recipients", default_factory=list)
|
||||
subject: str = SchemaField(description="Email subject", default="")
|
||||
body: str = SchemaField(description="Email body")
|
||||
attachments: list[MediaFileType] = SchemaField(
|
||||
description="Files to attach", default_factory=list, advanced=True
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
messageId: str = SchemaField(description="Sent message ID")
|
||||
threadId: str = SchemaField(description="Thread ID")
|
||||
message: dict = SchemaField(description="Raw Gmail message object")
|
||||
error: str = SchemaField(description="Error message if any")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="12bf5a24-9b90-4f40-9090-4e86e6995e60",
|
||||
description="Reply to a Gmail thread",
|
||||
categories={BlockCategory.COMMUNICATION},
|
||||
input_schema=GmailReplyBlock.Input,
|
||||
output_schema=GmailReplyBlock.Output,
|
||||
disabled=not GOOGLE_OAUTH_IS_CONFIGURED,
|
||||
test_input={
|
||||
"threadId": "t1",
|
||||
"parentMessageId": "m1",
|
||||
"body": "Thanks",
|
||||
"credentials": TEST_CREDENTIALS_INPUT,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS,
|
||||
test_output=[
|
||||
("messageId", "m2"),
|
||||
("threadId", "t1"),
|
||||
],
|
||||
test_mock={
|
||||
"_reply": lambda *args, **kwargs: {
|
||||
"id": "m2",
|
||||
"threadId": "t1",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: GoogleCredentials, graph_exec_id: str, **kwargs
|
||||
) -> BlockOutput:
|
||||
service = GmailReadBlock._build_service(credentials, **kwargs)
|
||||
message = self._reply(
|
||||
service,
|
||||
input_data,
|
||||
graph_exec_id,
|
||||
)
|
||||
yield "messageId", message["id"]
|
||||
yield "threadId", message.get("threadId", input_data.threadId)
|
||||
yield "message", message
|
||||
|
||||
def _reply(self, service, input_data: Input, graph_exec_id: str) -> dict:
|
||||
parent = (
|
||||
service.users()
|
||||
.messages()
|
||||
.get(
|
||||
userId="me",
|
||||
id=input_data.parentMessageId,
|
||||
format="metadata",
|
||||
metadataHeaders=["Subject", "References", "Message-ID"],
|
||||
)
|
||||
.execute()
|
||||
)
|
||||
headers = {h["name"].lower(): h["value"] for h in parent.get("payload", {}).get("headers", [])}
|
||||
subject = input_data.subject or (
|
||||
f"Re: {headers.get('subject', '')}".strip()
|
||||
)
|
||||
references = headers.get("references", "").split()
|
||||
if headers.get("message-id"):
|
||||
references.append(headers["message-id"])
|
||||
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email import encoders
|
||||
|
||||
msg = MIMEMultipart()
|
||||
if input_data.to:
|
||||
msg["To"] = ", ".join(input_data.to)
|
||||
if input_data.cc:
|
||||
msg["Cc"] = ", ".join(input_data.cc)
|
||||
if input_data.bcc:
|
||||
msg["Bcc"] = ", ".join(input_data.bcc)
|
||||
msg["Subject"] = subject
|
||||
if headers.get("message-id"):
|
||||
msg["In-Reply-To"] = headers["message-id"]
|
||||
if references:
|
||||
msg["References"] = " ".join(references)
|
||||
msg.attach(MIMEText(input_data.body, "html" if "<" in input_data.body else "plain"))
|
||||
|
||||
for attach in input_data.attachments:
|
||||
local_path = store_media_file(graph_exec_id, attach, return_content=False)
|
||||
abs_path = get_exec_file_path(graph_exec_id, local_path)
|
||||
part = MIMEBase("application", "octet-stream")
|
||||
with open(abs_path, "rb") as f:
|
||||
part.set_payload(f.read())
|
||||
encoders.encode_base64(part)
|
||||
part.add_header("Content-Disposition", f"attachment; filename={Path(abs_path).name}")
|
||||
msg.attach(part)
|
||||
|
||||
import base64
|
||||
|
||||
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode("utf-8")
|
||||
return (
|
||||
service.users()
|
||||
.messages()
|
||||
.send(userId="me", body={"threadId": input_data.threadId, "raw": raw})
|
||||
.execute()
|
||||
)
|
||||
|
||||
@@ -99,6 +99,8 @@ Below is a comprehensive list of all available blocks, categorized by their prim
|
||||
| Block Name | Description |
|
||||
|------------|-------------|
|
||||
| [Gmail Read](google/gmail.md#gmail-read) | Retrieves and reads emails from a Gmail account |
|
||||
| [Gmail Get Thread](google/gmail.md#gmail-get-thread) | Returns every message in a Gmail thread |
|
||||
| [Gmail Reply](google/gmail.md#gmail-reply) | Sends a reply that stays in the same thread |
|
||||
| [Gmail Send](google/gmail.md#gmail-send) | Sends emails using a Gmail account |
|
||||
| [Gmail List Labels](google/gmail.md#gmail-list-labels) | Retrieves all labels from a Gmail account |
|
||||
| [Gmail Add Label](google/gmail.md#gmail-add-label) | Adds a label to a specific email in a Gmail account |
|
||||
|
||||
@@ -21,7 +21,7 @@ The block connects to the user's Gmail account using their credentials, performs
|
||||
### Outputs
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| Email | Detailed information about a single email |
|
||||
| Email | Detailed information about a single email (now includes `threadId`) |
|
||||
| Emails | A list of email data for multiple emails |
|
||||
| Error | An error message if something goes wrong during the process |
|
||||
|
||||
@@ -141,4 +141,64 @@ The block first finds the ID of the specified label in the user's Gmail account.
|
||||
| Error | An error message if something goes wrong during the process |
|
||||
|
||||
### Possible use case
|
||||
Automatically removing the "Unread" label from emails after they have been processed by a customer service representative.
|
||||
Automatically removing the "Unread" label from emails after they have been processed by a customer service representative.
|
||||
|
||||
---
|
||||
|
||||
## Gmail Get Thread
|
||||
|
||||
### What it is
|
||||
A block that retrieves an entire Gmail thread.
|
||||
|
||||
### What it does
|
||||
Given a `threadId`, this block fetches all messages in that thread. You can optionally include messages in Spam and Trash.
|
||||
|
||||
### Inputs
|
||||
| Input | Description |
|
||||
|-------|-------------|
|
||||
| Credentials | The user's Gmail account credentials for authentication |
|
||||
| threadId | The ID of the thread to fetch |
|
||||
| includeSpamTrash | Whether to include messages from Spam and Trash |
|
||||
|
||||
### Outputs
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| Thread | The raw Gmail thread resource |
|
||||
| Error | An error message if something goes wrong |
|
||||
|
||||
### Possible use case
|
||||
Checking if a recipient replied in an existing conversation.
|
||||
|
||||
---
|
||||
|
||||
## Gmail Reply
|
||||
|
||||
### What it is
|
||||
A block that sends a reply within an existing Gmail thread.
|
||||
|
||||
### What it does
|
||||
This block builds a properly formatted reply email and sends it so Gmail keeps it in the same conversation.
|
||||
|
||||
### Inputs
|
||||
| Input | Description |
|
||||
|-------|-------------|
|
||||
| Credentials | The user's Gmail account credentials for authentication |
|
||||
| threadId | The thread to reply in |
|
||||
| parentMessageId | The ID of the message you are replying to |
|
||||
| To | List of recipients |
|
||||
| Cc | List of CC recipients |
|
||||
| Bcc | List of BCC recipients |
|
||||
| Subject | Optional subject (defaults to `Re:` prefix) |
|
||||
| Body | The email body |
|
||||
| Attachments | Optional files to include |
|
||||
|
||||
### Outputs
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| MessageId | The ID of the sent message |
|
||||
| ThreadId | The thread the reply belongs to |
|
||||
| Message | Full Gmail message object |
|
||||
| Error | Error message if something goes wrong |
|
||||
|
||||
### Possible use case
|
||||
Automatically respond "Thanks, see you then" to a scheduling email while keeping the conversation tidy.
|
||||
|
||||
Reference in New Issue
Block a user