Skip to content

Commit a7a2056

Browse files
aadamgoughAdam Gough
andauthored
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>
1 parent 1213a64 commit a7a2056

File tree

4 files changed

+215
-77
lines changed

4 files changed

+215
-77
lines changed

apps/docs/content/docs/tools/gmail.mdx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,46 @@ Draft emails using Gmail
102102
| `threadId` | string |
103103
| `labelIds` | string |
104104

105+
### `gmail_read`
106+
107+
Read emails from Gmail
108+
109+
#### Input
110+
111+
| Parameter | Type | Required | Description |
112+
| --------- | ---- | -------- | ----------- |
113+
| `accessToken` | string | Yes | Access token for Gmail API |
114+
| `messageId` | string | No | ID of the message to read |
115+
| `folder` | string | No | Folder/label to read emails from |
116+
| `unreadOnly` | boolean | No | Only retrieve unread messages |
117+
| `maxResults` | number | No | Maximum number of messages to retrieve \(default: 1, max: 10\) |
118+
119+
#### Output
120+
121+
| Parameter | Type |
122+
| --------- | ---- |
123+
| `content` | string |
124+
| `metadata` | string |
125+
126+
### `gmail_search`
127+
128+
Search emails in Gmail
129+
130+
#### Input
131+
132+
| Parameter | Type | Required | Description |
133+
| --------- | ---- | -------- | ----------- |
134+
| `accessToken` | string | Yes | Access token for Gmail API |
135+
| `query` | string | Yes | Search query for emails |
136+
| `maxResults` | number | No | Maximum number of results to return \(default: 1, max: 10\) |
137+
138+
#### Output
139+
140+
| Parameter | Type |
141+
| --------- | ---- |
142+
| `content` | string |
143+
| `metadata` | string |
144+
105145

106146

107147
## Block Configuration

apps/sim/blocks/blocks/gmail.ts

Lines changed: 63 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
2121
layout: 'full',
2222
options: [
2323
{ label: 'Send Email', id: 'send_gmail' },
24-
// { label: 'Read Email', id: 'read_gmail' },
24+
{ label: 'Read Email', id: 'read_gmail' },
2525
{ label: 'Draft Email', id: 'draft_gmail' },
26+
{ label: 'Search Email', id: 'search_gmail' },
2627
],
2728
},
2829
// Gmail Credentials
@@ -67,77 +68,73 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
6768
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
6869
},
6970
// Read Email Fields - Add folder selector
70-
// {
71-
// id: 'folder',
72-
// title: 'Label',
73-
// type: 'folder-selector',
74-
// layout: 'full',
75-
// provider: 'google-email',
76-
// serviceId: 'gmail',
77-
// requiredScopes: [
78-
// // 'https://www.googleapis.com/auth/gmail.readonly',
79-
// 'https://www.googleapis.com/auth/gmail.labels',
80-
// ],
81-
// placeholder: 'Select Gmail label/folder',
82-
// condition: { field: 'operation', value: 'read_gmail' },
83-
// },
84-
// {
85-
// id: 'unreadOnly',
86-
// title: 'Unread Only',
87-
// type: 'switch',
88-
// layout: 'full',
89-
// condition: { field: 'operation', value: 'read_gmail' },
90-
// },
91-
// {
92-
// id: 'maxResults',
93-
// title: 'Number of Emails',
94-
// type: 'short-input',
95-
// layout: 'full',
96-
// placeholder: 'Number of emails to retrieve (default: 1, max: 10)',
97-
// condition: { field: 'operation', value: 'read_gmail' },
98-
// },
99-
// {
100-
// id: 'messageId',
101-
// title: 'Message ID',
102-
// type: 'short-input',
103-
// layout: 'full',
104-
// placeholder: 'Enter message ID to read (optional)',
105-
// condition: {
106-
// field: 'operation',
107-
// value: 'read_gmail',
108-
// and: {
109-
// field: 'folder',
110-
// value: '',
111-
// },
112-
// },
113-
// },
114-
// // Search Fields
115-
// {
116-
// id: 'query',
117-
// title: 'Search Query',
118-
// type: 'short-input',
119-
// layout: 'full',
120-
// placeholder: 'Enter search terms',
121-
// condition: { field: 'operation', value: 'search_gmail' },
122-
// },
123-
// {
124-
// id: 'maxResults',
125-
// title: 'Max Results',
126-
// type: 'short-input',
127-
// layout: 'full',
128-
// placeholder: 'Maximum number of results (default: 10)',
129-
// condition: { field: 'operation', value: 'search_gmail' },
130-
// },
71+
{
72+
id: 'folder',
73+
title: 'Label',
74+
type: 'folder-selector',
75+
layout: 'full',
76+
provider: 'google-email',
77+
serviceId: 'gmail',
78+
requiredScopes: [
79+
'https://www.googleapis.com/auth/gmail.readonly',
80+
'https://www.googleapis.com/auth/gmail.labels',
81+
],
82+
placeholder: 'Select Gmail label/folder',
83+
condition: { field: 'operation', value: 'read_gmail' },
84+
},
85+
{
86+
id: 'unreadOnly',
87+
title: 'Unread Only',
88+
type: 'switch',
89+
layout: 'full',
90+
condition: { field: 'operation', value: 'read_gmail' },
91+
},
92+
{
93+
id: 'messageId',
94+
title: 'Message ID',
95+
type: 'short-input',
96+
layout: 'full',
97+
placeholder: 'Enter message ID to read (optional)',
98+
condition: {
99+
field: 'operation',
100+
value: 'read_gmail',
101+
and: {
102+
field: 'folder',
103+
value: '',
104+
},
105+
},
106+
},
107+
// Search Fields
108+
{
109+
id: 'query',
110+
title: 'Search Query',
111+
type: 'short-input',
112+
layout: 'full',
113+
placeholder: 'Enter search terms',
114+
condition: { field: 'operation', value: 'search_gmail' },
115+
},
116+
{
117+
id: 'maxResults',
118+
title: 'Max Results',
119+
type: 'short-input',
120+
layout: 'full',
121+
placeholder: 'Maximum number of results (default: 10)',
122+
condition: { field: 'operation', value: ['search_gmail', 'read_gmail'] },
123+
},
131124
],
132125
tools: {
133-
access: ['gmail_send', 'gmail_draft'],
126+
access: ['gmail_send', 'gmail_draft', 'gmail_read', 'gmail_search'],
134127
config: {
135128
tool: (params) => {
136129
switch (params.operation) {
137130
case 'send_gmail':
138131
return 'gmail_send'
139132
case 'draft_gmail':
140133
return 'gmail_draft'
134+
case 'search_gmail':
135+
return 'gmail_search'
136+
case 'read_gmail':
137+
return 'gmail_read'
141138
default:
142139
throw new Error(`Invalid Gmail operation: ${params.operation}`)
143140
}
@@ -146,9 +143,9 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
146143
// Pass the credential directly from the credential field
147144
const { credential, ...rest } = params
148145

149-
// Set default folder to INBOX if not specified
150-
if (rest.operation === 'read_gmail' && !rest.folder) {
151-
rest.folder = 'INBOX'
146+
// Ensure folder is always provided for read_gmail operation
147+
if (rest.operation === 'read_gmail') {
148+
rest.folder = rest.folder || 'INBOX'
152149
}
153150

154151
return {

apps/sim/tools/gmail/read.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const gmailReadTool: ToolConfig<GmailReadParams, GmailToolResponse> = {
3131
folder: {
3232
type: 'string',
3333
required: false,
34-
visibility: 'user-or-llm',
34+
visibility: 'user-only',
3535
description: 'Folder/label to read emails from',
3636
},
3737
unreadOnly: {

apps/sim/tools/gmail/search.ts

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,77 @@ export const gmailSearchTool: ToolConfig<GmailSearchParams, GmailToolResponse> =
5252
}),
5353
},
5454

55-
transformResponse: async (response) => {
55+
transformResponse: async (response, params) => {
5656
const data = await response.json()
5757

5858
if (!response.ok) {
5959
throw new Error(data.error?.message || 'Failed to search emails')
6060
}
6161

62-
return {
63-
success: true,
64-
output: {
65-
content: `Found ${data.messages?.length || 0} messages`,
66-
metadata: {
67-
results:
68-
data.messages?.map((msg: any) => ({
62+
if (!data.messages || data.messages.length === 0) {
63+
return {
64+
success: true,
65+
output: {
66+
content: 'No messages found matching your search query.',
67+
metadata: {
68+
results: [],
69+
},
70+
},
71+
}
72+
}
73+
74+
try {
75+
// Fetch full message details for each result
76+
const messagePromises = data.messages.map(async (msg: any) => {
77+
const messageResponse = await fetch(`${GMAIL_API_BASE}/messages/${msg.id}?format=full`, {
78+
headers: {
79+
Authorization: `Bearer ${params?.accessToken || ''}`,
80+
'Content-Type': 'application/json',
81+
},
82+
})
83+
84+
if (!messageResponse.ok) {
85+
throw new Error(`Failed to fetch details for message ${msg.id}`)
86+
}
87+
88+
return await messageResponse.json()
89+
})
90+
91+
const messages = await Promise.all(messagePromises)
92+
93+
// Process all messages and create a summary
94+
const processedMessages = messages.map(processMessageForSummary)
95+
96+
return {
97+
success: true,
98+
output: {
99+
content: createMessagesSummary(processedMessages),
100+
metadata: {
101+
results: processedMessages.map((msg) => ({
102+
id: msg.id,
103+
threadId: msg.threadId,
104+
subject: msg.subject,
105+
from: msg.from,
106+
date: msg.date,
107+
snippet: msg.snippet,
108+
})),
109+
},
110+
},
111+
}
112+
} catch (error: any) {
113+
console.error('Error fetching message details:', error)
114+
return {
115+
success: true,
116+
output: {
117+
content: `Found ${data.messages.length} messages but couldn't retrieve all details: ${error.message || 'Unknown error'}`,
118+
metadata: {
119+
results: data.messages.map((msg: any) => ({
69120
id: msg.id,
70121
threadId: msg.threadId,
71-
})) || [],
122+
})),
123+
},
72124
},
73-
},
125+
}
74126
}
75127
},
76128

@@ -87,3 +139,52 @@ export const gmailSearchTool: ToolConfig<GmailSearchParams, GmailToolResponse> =
87139
return error.message || 'An unexpected error occurred while searching emails'
88140
},
89141
}
142+
143+
// Helper function to process a message for summary (without full content)
144+
function processMessageForSummary(message: any): any {
145+
if (!message || !message.payload) {
146+
return {
147+
id: message?.id || '',
148+
threadId: message?.threadId || '',
149+
subject: 'Unknown Subject',
150+
from: 'Unknown Sender',
151+
date: '',
152+
snippet: message?.snippet || '',
153+
}
154+
}
155+
156+
const headers = message.payload.headers || []
157+
const subject =
158+
headers.find((h: any) => h.name.toLowerCase() === 'subject')?.value || 'No Subject'
159+
const from = headers.find((h: any) => h.name.toLowerCase() === 'from')?.value || 'Unknown Sender'
160+
const date = headers.find((h: any) => h.name.toLowerCase() === 'date')?.value || ''
161+
162+
return {
163+
id: message.id,
164+
threadId: message.threadId,
165+
subject,
166+
from,
167+
date,
168+
snippet: message.snippet || '',
169+
}
170+
}
171+
172+
// Helper function to create a summary of multiple messages
173+
function createMessagesSummary(messages: any[]): string {
174+
if (messages.length === 0) {
175+
return 'No messages found.'
176+
}
177+
178+
let summary = `Found ${messages.length} messages:\n\n`
179+
180+
messages.forEach((msg, index) => {
181+
summary += `${index + 1}. Subject: ${msg.subject}\n`
182+
summary += ` From: ${msg.from}\n`
183+
summary += ` Date: ${msg.date}\n`
184+
summary += ` Preview: ${msg.snippet}\n\n`
185+
})
186+
187+
summary += `To read full content of a specific message, use the gmail_read tool with messageId: ${messages.map((m) => m.id).join(', ')}`
188+
189+
return summary
190+
}

0 commit comments

Comments
 (0)