mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user