Improvement(gmail-tools): added search and read (#680)

* improvement: added search and read gmail

* fix: meta.json file unwanted changes

* fix: modified docs, removed duplication #680

---------

Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net>
This commit is contained in:
Adam Gough
2025-07-14 19:26:06 -07:00
committed by GitHub
parent 1213a64ecd
commit a7a2056b5f
4 changed files with 215 additions and 77 deletions

View File

@@ -102,6 +102,46 @@ Draft emails using Gmail
| `threadId` | string |
| `labelIds` | string |
### `gmail_read`
Read emails from Gmail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `accessToken` | string | Yes | Access token for Gmail API |
| `messageId` | string | No | ID of the message to read |
| `folder` | string | No | Folder/label to read emails from |
| `unreadOnly` | boolean | No | Only retrieve unread messages |
| `maxResults` | number | No | Maximum number of messages to retrieve \(default: 1, max: 10\) |
#### Output
| Parameter | Type |
| --------- | ---- |
| `content` | string |
| `metadata` | string |
### `gmail_search`
Search emails in Gmail
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `accessToken` | string | Yes | Access token for Gmail API |
| `query` | string | Yes | Search query for emails |
| `maxResults` | number | No | Maximum number of results to return \(default: 1, max: 10\) |
#### Output
| Parameter | Type |
| --------- | ---- |
| `content` | string |
| `metadata` | string |
## Block Configuration

View File

@@ -21,8 +21,9 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
layout: 'full',
options: [
{ label: 'Send Email', id: 'send_gmail' },
// { label: 'Read Email', id: 'read_gmail' },
{ label: 'Read Email', id: 'read_gmail' },
{ label: 'Draft Email', id: 'draft_gmail' },
{ label: 'Search Email', id: 'search_gmail' },
],
},
// Gmail Credentials
@@ -67,70 +68,62 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
},
// Read Email Fields - Add folder selector
// {
// id: 'folder',
// title: 'Label',
// type: 'folder-selector',
// layout: 'full',
// provider: 'google-email',
// serviceId: 'gmail',
// requiredScopes: [
// // 'https://www.googleapis.com/auth/gmail.readonly',
// 'https://www.googleapis.com/auth/gmail.labels',
// ],
// placeholder: 'Select Gmail label/folder',
// condition: { field: 'operation', value: 'read_gmail' },
// },
// {
// id: 'unreadOnly',
// title: 'Unread Only',
// type: 'switch',
// layout: 'full',
// condition: { field: 'operation', value: 'read_gmail' },
// },
// {
// id: 'maxResults',
// title: 'Number of Emails',
// type: 'short-input',
// layout: 'full',
// placeholder: 'Number of emails to retrieve (default: 1, max: 10)',
// condition: { field: 'operation', value: 'read_gmail' },
// },
// {
// id: 'messageId',
// title: 'Message ID',
// type: 'short-input',
// layout: 'full',
// placeholder: 'Enter message ID to read (optional)',
// condition: {
// field: 'operation',
// value: 'read_gmail',
// and: {
// field: 'folder',
// value: '',
// },
// },
// },
// // Search Fields
// {
// id: 'query',
// title: 'Search Query',
// type: 'short-input',
// layout: 'full',
// placeholder: 'Enter search terms',
// condition: { field: 'operation', value: 'search_gmail' },
// },
// {
// id: 'maxResults',
// title: 'Max Results',
// type: 'short-input',
// layout: 'full',
// placeholder: 'Maximum number of results (default: 10)',
// condition: { field: 'operation', value: 'search_gmail' },
// },
{
id: 'folder',
title: 'Label',
type: 'folder-selector',
layout: 'full',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.labels',
],
placeholder: 'Select Gmail label/folder',
condition: { field: 'operation', value: 'read_gmail' },
},
{
id: 'unreadOnly',
title: 'Unread Only',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'read_gmail' },
},
{
id: 'messageId',
title: 'Message ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter message ID to read (optional)',
condition: {
field: 'operation',
value: 'read_gmail',
and: {
field: 'folder',
value: '',
},
},
},
// Search Fields
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search terms',
condition: { field: 'operation', value: 'search_gmail' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: 'Maximum number of results (default: 10)',
condition: { field: 'operation', value: ['search_gmail', 'read_gmail'] },
},
],
tools: {
access: ['gmail_send', 'gmail_draft'],
access: ['gmail_send', 'gmail_draft', 'gmail_read', 'gmail_search'],
config: {
tool: (params) => {
switch (params.operation) {
@@ -138,6 +131,10 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
return 'gmail_send'
case 'draft_gmail':
return 'gmail_draft'
case 'search_gmail':
return 'gmail_search'
case 'read_gmail':
return 'gmail_read'
default:
throw new Error(`Invalid Gmail operation: ${params.operation}`)
}
@@ -146,9 +143,9 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
// Pass the credential directly from the credential field
const { credential, ...rest } = params
// Set default folder to INBOX if not specified
if (rest.operation === 'read_gmail' && !rest.folder) {
rest.folder = 'INBOX'
// Ensure folder is always provided for read_gmail operation
if (rest.operation === 'read_gmail') {
rest.folder = rest.folder || 'INBOX'
}
return {

View File

@@ -31,7 +31,7 @@ export const gmailReadTool: ToolConfig<GmailReadParams, GmailToolResponse> = {
folder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
visibility: 'user-only',
description: 'Folder/label to read emails from',
},
unreadOnly: {

View File

@@ -52,25 +52,77 @@ export const gmailSearchTool: ToolConfig<GmailSearchParams, GmailToolResponse> =
}),
},
transformResponse: async (response) => {
transformResponse: async (response, params) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to search emails')
}
return {
success: true,
output: {
content: `Found ${data.messages?.length || 0} messages`,
metadata: {
results:
data.messages?.map((msg: any) => ({
if (!data.messages || data.messages.length === 0) {
return {
success: true,
output: {
content: 'No messages found matching your search query.',
metadata: {
results: [],
},
},
}
}
try {
// Fetch full message details for each result
const messagePromises = data.messages.map(async (msg: any) => {
const messageResponse = await fetch(`${GMAIL_API_BASE}/messages/${msg.id}?format=full`, {
headers: {
Authorization: `Bearer ${params?.accessToken || ''}`,
'Content-Type': 'application/json',
},
})
if (!messageResponse.ok) {
throw new Error(`Failed to fetch details for message ${msg.id}`)
}
return await messageResponse.json()
})
const messages = await Promise.all(messagePromises)
// Process all messages and create a summary
const processedMessages = messages.map(processMessageForSummary)
return {
success: true,
output: {
content: createMessagesSummary(processedMessages),
metadata: {
results: processedMessages.map((msg) => ({
id: msg.id,
threadId: msg.threadId,
})) || [],
subject: msg.subject,
from: msg.from,
date: msg.date,
snippet: msg.snippet,
})),
},
},
},
}
} catch (error: any) {
console.error('Error fetching message details:', error)
return {
success: true,
output: {
content: `Found ${data.messages.length} messages but couldn't retrieve all details: ${error.message || 'Unknown error'}`,
metadata: {
results: data.messages.map((msg: any) => ({
id: msg.id,
threadId: msg.threadId,
})),
},
},
}
}
},
@@ -87,3 +139,52 @@ export const gmailSearchTool: ToolConfig<GmailSearchParams, GmailToolResponse> =
return error.message || 'An unexpected error occurred while searching emails'
},
}
// Helper function to process a message for summary (without full content)
function processMessageForSummary(message: any): any {
if (!message || !message.payload) {
return {
id: message?.id || '',
threadId: message?.threadId || '',
subject: 'Unknown Subject',
from: 'Unknown Sender',
date: '',
snippet: message?.snippet || '',
}
}
const headers = message.payload.headers || []
const subject =
headers.find((h: any) => h.name.toLowerCase() === 'subject')?.value || 'No Subject'
const from = headers.find((h: any) => h.name.toLowerCase() === 'from')?.value || 'Unknown Sender'
const date = headers.find((h: any) => h.name.toLowerCase() === 'date')?.value || ''
return {
id: message.id,
threadId: message.threadId,
subject,
from,
date,
snippet: message.snippet || '',
}
}
// Helper function to create a summary of multiple messages
function createMessagesSummary(messages: any[]): string {
if (messages.length === 0) {
return 'No messages found.'
}
let summary = `Found ${messages.length} messages:\n\n`
messages.forEach((msg, index) => {
summary += `${index + 1}. Subject: ${msg.subject}\n`
summary += ` From: ${msg.from}\n`
summary += ` Date: ${msg.date}\n`
summary += ` Preview: ${msg.snippet}\n\n`
})
summary += `To read full content of a specific message, use the gmail_read tool with messageId: ${messages.map((m) => m.id).join(', ')}`
return summary
}