Added gmail tool to send/reach/search gmail, added condition display of sub-blocks. Need to integrate oauth2 credentials for gmail

This commit is contained in:
Waleed Latif
2025-02-10 10:54:52 -08:00
parent 980ca6d116
commit 4b92eb0209
11 changed files with 516 additions and 7 deletions
+88
View File
@@ -0,0 +1,88 @@
import { ToolConfig } from '../types'
import { GmailMessage, GmailReadParams, GmailToolResponse } from './types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
export const gmailReadTool: ToolConfig<GmailReadParams, GmailToolResponse> = {
id: 'gmail_read',
name: 'Gmail Read',
description: 'Read emails from Gmail',
version: '1.0.0',
params: {
accessToken: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'OAuth access token for Gmail API',
},
messageId: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'ID of the message to read',
},
},
request: {
url: (params: GmailReadParams) => `${GMAIL_API_BASE}/messages/${params.messageId}`,
method: 'GET',
headers: (params: GmailReadParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to read email')
}
const message = data as GmailMessage
const headers = message.payload.headers
const subject = headers.find((h) => h.name.toLowerCase() === 'subject')?.value
const from = headers.find((h) => h.name.toLowerCase() === 'from')?.value
const to = headers.find((h) => h.name.toLowerCase() === 'to')?.value
let body = ''
if (message.payload.body?.data) {
body = Buffer.from(message.payload.body.data, 'base64').toString()
} else if (message.payload.parts) {
const textPart = message.payload.parts.find((p) => p.mimeType === 'text/plain')
if (textPart?.body?.data) {
body = Buffer.from(textPart.body.data, 'base64').toString()
}
}
return {
success: true,
output: {
content: body,
metadata: {
id: message.id,
threadId: message.threadId,
labelIds: message.labelIds,
from,
to,
subject,
},
},
}
},
transformError: (error) => {
// Handle Google API error format
if (error.error?.message) {
if (error.error.message.includes('invalid authentication credentials')) {
return 'Invalid or expired access token. Please reauthenticate.'
}
if (error.error.message.includes('quota')) {
return 'Gmail API quota exceeded. Please try again later.'
}
return error.error.message
}
return error.message || 'An unexpected error occurred while reading email'
},
}
+82
View File
@@ -0,0 +1,82 @@
import { ToolConfig } from '../types'
import { GmailSearchParams, GmailToolResponse } from './types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
export const gmailSearchTool: ToolConfig<GmailSearchParams, GmailToolResponse> = {
id: 'gmail_search',
name: 'Gmail Search',
description: 'Search emails in Gmail',
version: '1.0.0',
params: {
accessToken: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'OAuth access token for Gmail API',
},
query: {
type: 'string',
required: true,
description: 'Search query for emails',
},
maxResults: {
type: 'number',
required: false,
description: 'Maximum number of results to return',
},
},
request: {
url: (params: GmailSearchParams) => {
const searchParams = new URLSearchParams()
searchParams.append('q', params.query)
if (params.maxResults) {
searchParams.append('maxResults', params.maxResults.toString())
}
return `${GMAIL_API_BASE}/messages?${searchParams.toString()}`
},
method: 'GET',
headers: (params: GmailSearchParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
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) => ({
id: msg.id,
threadId: msg.threadId,
})) || [],
},
},
}
},
transformError: (error) => {
// Handle Google API error format
if (error.error?.message) {
if (error.error.message.includes('invalid authentication credentials')) {
return 'Invalid or expired access token. Please reauthenticate.'
}
if (error.error.message.includes('quota')) {
return 'Gmail API quota exceeded. Please try again later.'
}
return error.error.message
}
return error.message || 'An unexpected error occurred while searching emails'
},
}
+93
View File
@@ -0,0 +1,93 @@
import { ToolConfig } from '../types'
import { GmailSendParams, GmailToolResponse } from './types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
export const gmailSendTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
id: 'gmail_send',
name: 'Gmail Send',
description: 'Send emails using Gmail',
version: '1.0.0',
params: {
accessToken: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'OAuth access token for Gmail API',
},
to: {
type: 'string',
required: true,
requiredForToolCall: true,
description: 'Recipient email address',
},
subject: {
type: 'string',
required: true,
description: 'Email subject',
},
body: {
type: 'string',
required: true,
description: 'Email body content',
},
},
request: {
url: () => `${GMAIL_API_BASE}/messages/send`,
method: 'POST',
headers: (params: GmailSendParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GmailSendParams): Record<string, any> => {
const email = [
'Content-Type: text/plain; charset="UTF-8"',
'MIME-Version: 1.0',
`To: ${params.to}`,
`Subject: ${params.subject}`,
'',
params.body,
].join('\n')
return {
raw: Buffer.from(email).toString('base64url'),
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to send email')
}
return {
success: true,
output: {
content: 'Email sent successfully',
metadata: {
id: data.id,
threadId: data.threadId,
labelIds: data.labelIds,
},
},
}
},
transformError: (error) => {
// Handle Google API error format
if (error.error?.message) {
if (error.error.message.includes('invalid authentication credentials')) {
return 'Invalid or expired access token. Please reauthenticate.'
}
if (error.error.message.includes('quota')) {
return 'Gmail API quota exceeded. Please try again later.'
}
return error.error.message
}
return error.message || 'An unexpected error occurred while sending email'
},
}
+78
View File
@@ -0,0 +1,78 @@
import { ToolResponse } from '../types'
// Base parameters shared by all operations
interface BaseGmailParams {
accessToken: string
}
// Send operation parameters
export interface GmailSendParams extends BaseGmailParams {
to: string
subject: string
body: string
}
// Read operation parameters
export interface GmailReadParams extends BaseGmailParams {
messageId: string
}
// Search operation parameters
export interface GmailSearchParams extends BaseGmailParams {
query: string
maxResults?: number
}
// Union type for all Gmail tool parameters
export type GmailToolParams = GmailSendParams | GmailReadParams | GmailSearchParams
// Response metadata
interface BaseGmailMetadata {
id?: string
threadId?: string
labelIds?: string[]
}
interface EmailMetadata extends BaseGmailMetadata {
from?: string
to?: string
subject?: string
}
interface SearchMetadata extends BaseGmailMetadata {
results: Array<{
id: string
threadId: string
}>
}
// Response format
export interface GmailToolResponse extends ToolResponse {
output: {
content: string
metadata: EmailMetadata | SearchMetadata
}
}
// Email Message Interface
export interface GmailMessage {
id: string
threadId: string
labelIds: string[]
snippet: string
payload: {
headers: Array<{
name: string
value: string
}>
body: {
data?: string
}
parts?: Array<{
mimeType: string
body: {
data?: string
}
}>
}
}
+6
View File
@@ -5,6 +5,9 @@ import { reasonerTool as deepseekReasoner } from './deepseek/reasoner'
import { scrapeTool } from './firecrawl/scrape'
import { functionExecuteTool as functionExecute } from './function/execute'
import { repoInfoTool } from './github/repo'
import { gmailReadTool } from './gmail/read'
import { gmailSearchTool } from './gmail/search'
import { gmailSendTool } from './gmail/send'
import { chatTool as googleChat } from './google/chat'
import { requestTool as httpRequest } from './http/request'
import { contactsTool as hubspotContacts } from './hubspot/contacts'
@@ -44,6 +47,9 @@ export const tools: Record<string, ToolConfig> = {
youtube_search: youtubeSearchTool,
notion_read: notionReadTool,
notion_write: notionWriteTool,
gmail_send: gmailSendTool,
gmail_read: gmailReadTool,
gmail_search: gmailSearchTool,
}
// Get a tool by its ID