mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
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:
@@ -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'
|
||||
},
|
||||
}
|
||||
@@ -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'
|
||||
},
|
||||
}
|
||||
@@ -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'
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user