feat(google): add missing tools for Gmail, Drive, Sheets, and Calendar (#3338)

* feat(google): add missing tools for Gmail, Drive, Sheets, and Calendar

* fix(google-drive): remove dead transformResponse from move tool
This commit is contained in:
Waleed
2026-02-25 13:38:35 -08:00
committed by GitHub
parent 063ec87ced
commit 16f337f6fd
23 changed files with 1956 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailCreateLabelParams {
accessToken: string
name: string
messageListVisibility?: string
labelListVisibility?: string
}
interface GmailCreateLabelResponse {
success: boolean
output: {
id: string
name: string
messageListVisibility?: string
labelListVisibility?: string
type?: string
}
}
export const gmailCreateLabelV2Tool: ToolConfig<GmailCreateLabelParams, GmailCreateLabelResponse> =
{
id: 'gmail_create_label_v2',
name: 'Gmail Create Label',
description: 'Create a new label in Gmail',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Display name for the new label',
},
messageListVisibility: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Visibility of messages with this label in the message list (show or hide)',
},
labelListVisibility: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Visibility of the label in the label list (labelShow, labelShowIfUnread, or labelHide)',
},
},
request: {
url: () => `${GMAIL_API_BASE}/labels`,
method: 'POST',
headers: (params: GmailCreateLabelParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GmailCreateLabelParams) => {
const body: Record<string, string> = { name: params.name }
if (params.messageListVisibility) {
body.messageListVisibility = params.messageListVisibility
}
if (params.labelListVisibility) {
body.labelListVisibility = params.labelListVisibility
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { id: '', name: '' },
error: data.error?.message || 'Failed to create label',
}
}
return {
success: true,
output: {
id: data.id,
name: data.name,
messageListVisibility: data.messageListVisibility ?? null,
labelListVisibility: data.labelListVisibility ?? null,
type: data.type ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Label ID' },
name: { type: 'string', description: 'Label display name' },
messageListVisibility: {
type: 'string',
description: 'Visibility of messages with this label',
optional: true,
},
labelListVisibility: {
type: 'string',
description: 'Visibility of the label in the label list',
optional: true,
},
type: { type: 'string', description: 'Label type (system or user)', optional: true },
},
}

View File

@@ -0,0 +1,76 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailDeleteDraftParams {
accessToken: string
draftId: string
}
interface GmailDeleteDraftResponse {
success: boolean
output: {
deleted: boolean
draftId: string
}
}
export const gmailDeleteDraftV2Tool: ToolConfig<GmailDeleteDraftParams, GmailDeleteDraftResponse> =
{
id: 'gmail_delete_draft_v2',
name: 'Gmail Delete Draft',
description: 'Delete a specific draft from Gmail',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to delete',
},
},
request: {
url: (params: GmailDeleteDraftParams) => `${GMAIL_API_BASE}/drafts/${params.draftId}`,
method: 'DELETE',
headers: (params: GmailDeleteDraftParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response, params?: GmailDeleteDraftParams) => {
if (!response.ok) {
const data = await response.json()
return {
success: false,
output: { deleted: false, draftId: params?.draftId ?? '' },
error: data.error?.message || 'Failed to delete draft',
}
}
return {
success: true,
output: {
deleted: true,
draftId: params?.draftId ?? '',
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the draft was successfully deleted' },
draftId: { type: 'string', description: 'ID of the deleted draft' },
},
}

View File

@@ -0,0 +1,76 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailDeleteLabelParams {
accessToken: string
labelId: string
}
interface GmailDeleteLabelResponse {
success: boolean
output: {
deleted: boolean
labelId: string
}
}
export const gmailDeleteLabelV2Tool: ToolConfig<GmailDeleteLabelParams, GmailDeleteLabelResponse> =
{
id: 'gmail_delete_label_v2',
name: 'Gmail Delete Label',
description: 'Delete a label from Gmail',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the label to delete',
},
},
request: {
url: (params: GmailDeleteLabelParams) => `${GMAIL_API_BASE}/labels/${params.labelId}`,
method: 'DELETE',
headers: (params: GmailDeleteLabelParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response, params?: GmailDeleteLabelParams) => {
if (!response.ok) {
const data = await response.json()
return {
success: false,
output: { deleted: false, labelId: params?.labelId ?? '' },
error: data.error?.message || 'Failed to delete label',
}
}
return {
success: true,
output: {
deleted: true,
labelId: params?.labelId ?? '',
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the label was successfully deleted' },
labelId: { type: 'string', description: 'ID of the deleted label' },
},
}

View File

@@ -0,0 +1,117 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailGetDraftParams {
accessToken: string
draftId: string
}
interface GmailGetDraftResponse {
success: boolean
output: {
id: string
messageId?: string
threadId?: string
to?: string
from?: string
subject?: string
body?: string
labelIds?: string[]
}
}
export const gmailGetDraftV2Tool: ToolConfig<GmailGetDraftParams, GmailGetDraftResponse> = {
id: 'gmail_get_draft_v2',
name: 'Gmail Get Draft',
description: 'Get a specific draft from Gmail by its ID',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
draftId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the draft to retrieve',
},
},
request: {
url: (params: GmailGetDraftParams) => `${GMAIL_API_BASE}/drafts/${params.draftId}?format=full`,
method: 'GET',
headers: (params: GmailGetDraftParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { id: '' },
error: data.error?.message || 'Failed to get draft',
}
}
const message = data.message || {}
const headers = message.payload?.headers || []
const getHeader = (name: string): string | undefined =>
headers.find((h: Record<string, string>) => h.name.toLowerCase() === name.toLowerCase())
?.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(
(part: Record<string, unknown>) => part.mimeType === 'text/plain'
)
if (textPart?.body?.data) {
body = Buffer.from(textPart.body.data, 'base64').toString()
}
}
return {
success: true,
output: {
id: data.id,
messageId: message.id ?? undefined,
threadId: message.threadId ?? undefined,
to: getHeader('To'),
from: getHeader('From'),
subject: getHeader('Subject'),
body: body || undefined,
labelIds: message.labelIds ?? undefined,
},
}
},
outputs: {
id: { type: 'string', description: 'Draft ID' },
messageId: { type: 'string', description: 'Gmail message ID', optional: true },
threadId: { type: 'string', description: 'Gmail thread ID', optional: true },
to: { type: 'string', description: 'Recipient email address', optional: true },
from: { type: 'string', description: 'Sender email address', optional: true },
subject: { type: 'string', description: 'Draft subject', optional: true },
body: { type: 'string', description: 'Draft body text', optional: true },
labelIds: {
type: 'array',
items: { type: 'string' },
description: 'Draft labels',
optional: true,
},
},
}

View File

@@ -0,0 +1,136 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailGetThreadParams {
accessToken: string
threadId: string
format?: string
}
interface GmailGetThreadResponse {
success: boolean
output: {
id: string
historyId?: string
messages: Array<{
id: string
threadId: string
labelIds?: string[]
snippet?: string
from?: string
to?: string
subject?: string
date?: string
body?: string
}>
}
}
export const gmailGetThreadV2Tool: ToolConfig<GmailGetThreadParams, GmailGetThreadResponse> = {
id: 'gmail_get_thread_v2',
name: 'Gmail Get Thread',
description: 'Get a specific email thread from Gmail, including all messages in the thread',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to retrieve',
},
format: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Format to return the messages in (full, metadata, or minimal). Defaults to full.',
},
},
request: {
url: (params: GmailGetThreadParams) => {
const format = params.format || 'full'
return `${GMAIL_API_BASE}/threads/${params.threadId}?format=${format}`
},
method: 'GET',
headers: (params: GmailGetThreadParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { id: '', messages: [] },
error: data.error?.message || 'Failed to get thread',
}
}
const messages = (data.messages || []).map((message: Record<string, unknown>) => {
const payload = message.payload as Record<string, unknown> | undefined
const headers = (payload?.headers as Array<Record<string, string>>) || []
const getHeader = (name: string): string | undefined =>
headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value
let body = ''
const payloadBody = payload?.body as Record<string, unknown> | undefined
if (payloadBody?.data) {
body = Buffer.from(payloadBody.data as string, 'base64').toString()
} else if (payload?.parts) {
const parts = payload.parts as Array<Record<string, unknown>>
const textPart = parts.find((part) => part.mimeType === 'text/plain')
const textBody = textPart?.body as Record<string, unknown> | undefined
if (textBody?.data) {
body = Buffer.from(textBody.data as string, 'base64').toString()
}
}
return {
id: message.id,
threadId: message.threadId,
labelIds: message.labelIds ?? null,
snippet: message.snippet ?? null,
from: getHeader('From') ?? null,
to: getHeader('To') ?? null,
subject: getHeader('Subject') ?? null,
date: getHeader('Date') ?? null,
body: body || null,
}
})
return {
success: true,
output: {
id: data.id,
historyId: data.historyId ?? null,
messages,
},
}
},
outputs: {
id: { type: 'string', description: 'Thread ID' },
historyId: { type: 'string', description: 'History ID', optional: true },
messages: {
type: 'json',
description:
'Array of messages in the thread with id, from, to, subject, date, body, and labels',
},
},
}

View File

@@ -1,7 +1,15 @@
import { gmailAddLabelTool, gmailAddLabelV2Tool } from '@/tools/gmail/add_label'
import { gmailArchiveTool, gmailArchiveV2Tool } from '@/tools/gmail/archive'
import { gmailCreateLabelV2Tool } from '@/tools/gmail/create_label'
import { gmailDeleteTool, gmailDeleteV2Tool } from '@/tools/gmail/delete'
import { gmailDeleteDraftV2Tool } from '@/tools/gmail/delete_draft'
import { gmailDeleteLabelV2Tool } from '@/tools/gmail/delete_label'
import { gmailDraftTool, gmailDraftV2Tool } from '@/tools/gmail/draft'
import { gmailGetDraftV2Tool } from '@/tools/gmail/get_draft'
import { gmailGetThreadV2Tool } from '@/tools/gmail/get_thread'
import { gmailListDraftsV2Tool } from '@/tools/gmail/list_drafts'
import { gmailListLabelsV2Tool } from '@/tools/gmail/list_labels'
import { gmailListThreadsV2Tool } from '@/tools/gmail/list_threads'
import { gmailMarkReadTool, gmailMarkReadV2Tool } from '@/tools/gmail/mark_read'
import { gmailMarkUnreadTool, gmailMarkUnreadV2Tool } from '@/tools/gmail/mark_unread'
import { gmailMoveTool, gmailMoveV2Tool } from '@/tools/gmail/move'
@@ -9,7 +17,9 @@ import { gmailReadTool, gmailReadV2Tool } from '@/tools/gmail/read'
import { gmailRemoveLabelTool, gmailRemoveLabelV2Tool } from '@/tools/gmail/remove_label'
import { gmailSearchTool, gmailSearchV2Tool } from '@/tools/gmail/search'
import { gmailSendTool, gmailSendV2Tool } from '@/tools/gmail/send'
import { gmailTrashThreadV2Tool } from '@/tools/gmail/trash_thread'
import { gmailUnarchiveTool, gmailUnarchiveV2Tool } from '@/tools/gmail/unarchive'
import { gmailUntrashThreadV2Tool } from '@/tools/gmail/untrash_thread'
export {
gmailSendTool,
@@ -36,4 +46,14 @@ export {
gmailAddLabelV2Tool,
gmailRemoveLabelTool,
gmailRemoveLabelV2Tool,
gmailListDraftsV2Tool,
gmailGetDraftV2Tool,
gmailDeleteDraftV2Tool,
gmailCreateLabelV2Tool,
gmailDeleteLabelV2Tool,
gmailListLabelsV2Tool,
gmailGetThreadV2Tool,
gmailListThreadsV2Tool,
gmailTrashThreadV2Tool,
gmailUntrashThreadV2Tool,
}

View File

@@ -0,0 +1,126 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailListDraftsParams {
accessToken: string
maxResults?: number
pageToken?: string
query?: string
}
interface GmailListDraftsResponse {
success: boolean
output: {
drafts: Array<{
id: string
messageId: string
threadId: string
}>
resultSizeEstimate: number
nextPageToken?: string
}
}
export const gmailListDraftsV2Tool: ToolConfig<GmailListDraftsParams, GmailListDraftsResponse> = {
id: 'gmail_list_drafts_v2',
name: 'Gmail List Drafts',
description: 'List all drafts in a Gmail account',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of drafts to return (default: 100, max: 500)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token for paginated results',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to filter drafts (same syntax as Gmail search)',
},
},
request: {
url: (params: GmailListDraftsParams) => {
const searchParams = new URLSearchParams()
if (params.maxResults) {
searchParams.append('maxResults', Number(params.maxResults).toString())
}
if (params.pageToken) {
searchParams.append('pageToken', params.pageToken)
}
if (params.query) {
searchParams.append('q', params.query)
}
const qs = searchParams.toString()
return `${GMAIL_API_BASE}/drafts${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params: GmailListDraftsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { drafts: [], resultSizeEstimate: 0 },
error: data.error?.message || 'Failed to list drafts',
}
}
const drafts = (data.drafts || []).map((draft: Record<string, unknown>) => ({
id: draft.id,
messageId: (draft.message as Record<string, unknown>)?.id ?? null,
threadId: (draft.message as Record<string, unknown>)?.threadId ?? null,
}))
return {
success: true,
output: {
drafts,
resultSizeEstimate: data.resultSizeEstimate ?? 0,
nextPageToken: data.nextPageToken ?? null,
},
}
},
outputs: {
drafts: {
type: 'json',
description: 'Array of draft objects with id, messageId, and threadId',
},
resultSizeEstimate: {
type: 'number',
description: 'Estimated total number of drafts',
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,81 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailListLabelsParams {
accessToken: string
}
interface GmailListLabelsResponse {
success: boolean
output: {
labels: Array<{
id: string
name: string
type: string
messageListVisibility?: string
labelListVisibility?: string
}>
}
}
export const gmailListLabelsV2Tool: ToolConfig<GmailListLabelsParams, GmailListLabelsResponse> = {
id: 'gmail_list_labels_v2',
name: 'Gmail List Labels',
description: 'List all labels in a Gmail account',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
},
request: {
url: () => `${GMAIL_API_BASE}/labels`,
method: 'GET',
headers: (params: GmailListLabelsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { labels: [] },
error: data.error?.message || 'Failed to list labels',
}
}
const labels = (data.labels || []).map((label: Record<string, unknown>) => ({
id: label.id,
name: label.name,
type: label.type ?? null,
messageListVisibility: label.messageListVisibility ?? null,
labelListVisibility: label.labelListVisibility ?? null,
}))
return {
success: true,
output: { labels },
}
},
outputs: {
labels: {
type: 'json',
description: 'Array of label objects with id, name, type, and visibility settings',
},
},
}

View File

@@ -0,0 +1,140 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailListThreadsParams {
accessToken: string
maxResults?: number
pageToken?: string
query?: string
labelIds?: string
}
interface GmailListThreadsResponse {
success: boolean
output: {
threads: Array<{
id: string
snippet: string
historyId: string
}>
resultSizeEstimate: number
nextPageToken?: string
}
}
export const gmailListThreadsV2Tool: ToolConfig<GmailListThreadsParams, GmailListThreadsResponse> =
{
id: 'gmail_list_threads_v2',
name: 'Gmail List Threads',
description: 'List email threads in a Gmail account',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of threads to return (default: 100, max: 500)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token for paginated results',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to filter threads (same syntax as Gmail search)',
},
labelIds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated label IDs to filter threads by',
},
},
request: {
url: (params: GmailListThreadsParams) => {
const searchParams = new URLSearchParams()
if (params.maxResults) {
searchParams.append('maxResults', Number(params.maxResults).toString())
}
if (params.pageToken) {
searchParams.append('pageToken', params.pageToken)
}
if (params.query) {
searchParams.append('q', params.query)
}
if (params.labelIds) {
const labels = params.labelIds.split(',').map((l) => l.trim())
for (const label of labels) {
searchParams.append('labelIds', label)
}
}
const qs = searchParams.toString()
return `${GMAIL_API_BASE}/threads${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params: GmailListThreadsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { threads: [], resultSizeEstimate: 0 },
error: data.error?.message || 'Failed to list threads',
}
}
const threads = (data.threads || []).map((thread: Record<string, unknown>) => ({
id: thread.id,
snippet: thread.snippet ?? '',
historyId: thread.historyId ?? '',
}))
return {
success: true,
output: {
threads,
resultSizeEstimate: data.resultSizeEstimate ?? 0,
nextPageToken: data.nextPageToken ?? null,
},
}
},
outputs: {
threads: {
type: 'json',
description: 'Array of thread objects with id, snippet, and historyId',
},
resultSizeEstimate: {
type: 'number',
description: 'Estimated total number of threads',
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,78 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailTrashThreadParams {
accessToken: string
threadId: string
}
interface GmailTrashThreadResponse {
success: boolean
output: {
id: string
trashed: boolean
}
}
export const gmailTrashThreadV2Tool: ToolConfig<GmailTrashThreadParams, GmailTrashThreadResponse> =
{
id: 'gmail_trash_thread_v2',
name: 'Gmail Trash Thread',
description: 'Move an email thread to trash in Gmail',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to trash',
},
},
request: {
url: (params: GmailTrashThreadParams) => `${GMAIL_API_BASE}/threads/${params.threadId}/trash`,
method: 'POST',
headers: (params: GmailTrashThreadParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { id: '', trashed: false },
error: data.error?.message || 'Failed to trash thread',
}
}
return {
success: true,
output: {
id: data.id,
trashed: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Thread ID' },
trashed: { type: 'boolean', description: 'Whether the thread was successfully trashed' },
},
}

View File

@@ -0,0 +1,84 @@
import { GMAIL_API_BASE } from '@/tools/gmail/utils'
import type { ToolConfig } from '@/tools/types'
interface GmailUntrashThreadParams {
accessToken: string
threadId: string
}
interface GmailUntrashThreadResponse {
success: boolean
output: {
id: string
untrashed: boolean
}
}
export const gmailUntrashThreadV2Tool: ToolConfig<
GmailUntrashThreadParams,
GmailUntrashThreadResponse
> = {
id: 'gmail_untrash_thread_v2',
name: 'Gmail Untrash Thread',
description: 'Remove an email thread from trash in Gmail',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-email',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Gmail API',
},
threadId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the thread to untrash',
},
},
request: {
url: (params: GmailUntrashThreadParams) =>
`${GMAIL_API_BASE}/threads/${params.threadId}/untrash`,
method: 'POST',
headers: (params: GmailUntrashThreadParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
output: { id: '', untrashed: false },
error: data.error?.message || 'Failed to untrash thread',
}
}
return {
success: true,
output: {
id: data.id,
untrashed: true,
},
}
},
outputs: {
id: { type: 'string', description: 'Thread ID' },
untrashed: {
type: 'boolean',
description: 'Whether the thread was successfully removed from trash',
},
},
}

View File

@@ -0,0 +1,155 @@
import {
CALENDAR_API_BASE,
type GoogleCalendarApiFreeBusyResponse,
type GoogleCalendarFreeBusyParams,
type GoogleCalendarFreeBusyResponse,
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const freebusyTool: ToolConfig<
GoogleCalendarFreeBusyParams,
GoogleCalendarFreeBusyResponse
> = {
id: 'google_calendar_freebusy',
name: 'Google Calendar Free/Busy',
description: 'Query free/busy information for one or more Google Calendars',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
calendarIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated calendar IDs to query (e.g., "primary,other@example.com")',
},
timeMin: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start of the time range (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
},
timeMax: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End of the time range (RFC3339 timestamp, e.g., 2025-06-04T00:00:00Z)',
},
timeZone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'IANA time zone (e.g., "UTC", "America/New_York"). Defaults to UTC.',
},
},
request: {
url: () => `${CALENDAR_API_BASE}/freeBusy`,
method: 'POST',
headers: (params: GoogleCalendarFreeBusyParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleCalendarFreeBusyParams) => {
const ids = params.calendarIds
.split(',')
.map((id) => id.trim())
.filter(Boolean)
return {
timeMin: params.timeMin,
timeMax: params.timeMax,
timeZone: params.timeZone || 'UTC',
items: ids.map((id) => ({ id })),
}
},
},
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiFreeBusyResponse = await response.json()
const calendarIds = Object.keys(data.calendars || {})
const totalBusy = calendarIds.reduce((sum, id) => {
return sum + (data.calendars[id]?.busy?.length || 0)
}, 0)
return {
success: true,
output: {
content: `Found ${totalBusy} busy period${totalBusy !== 1 ? 's' : ''} across ${calendarIds.length} calendar${calendarIds.length !== 1 ? 's' : ''}`,
metadata: {
timeMin: data.timeMin,
timeMax: data.timeMax,
calendars: data.calendars,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Summary of free/busy results' },
metadata: {
type: 'json',
description: 'Free/busy data with time range and per-calendar busy periods',
},
},
}
interface GoogleCalendarFreeBusyV2Response {
success: boolean
output: {
timeMin: string
timeMax: string
calendars: Record<
string,
{
busy: Array<{ start: string; end: string }>
errors?: Array<{ domain: string; reason: string }>
}
>
}
}
export const freebusyV2Tool: ToolConfig<
GoogleCalendarFreeBusyParams,
GoogleCalendarFreeBusyV2Response
> = {
id: 'google_calendar_freebusy_v2',
name: 'Google Calendar Free/Busy',
description:
'Query free/busy information for one or more Google Calendars. Returns API-aligned fields only.',
version: '2.0.0',
oauth: freebusyTool.oauth,
params: freebusyTool.params,
request: freebusyTool.request,
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiFreeBusyResponse = await response.json()
return {
success: true,
output: {
timeMin: data.timeMin,
timeMax: data.timeMax,
calendars: data.calendars,
},
}
},
outputs: {
timeMin: { type: 'string', description: 'Start of the queried time range' },
timeMax: { type: 'string', description: 'End of the queried time range' },
calendars: {
type: 'json',
description: 'Per-calendar free/busy data with busy periods and any errors',
},
},
}

View File

@@ -1,5 +1,6 @@
import { createTool, createV2Tool } from '@/tools/google_calendar/create'
import { deleteTool, deleteV2Tool } from '@/tools/google_calendar/delete'
import { freebusyTool, freebusyV2Tool } from '@/tools/google_calendar/freebusy'
import { getTool, getV2Tool } from '@/tools/google_calendar/get'
import { instancesTool, instancesV2Tool } from '@/tools/google_calendar/instances'
import { inviteTool, inviteV2Tool } from '@/tools/google_calendar/invite'
@@ -11,6 +12,7 @@ import { updateTool, updateV2Tool } from '@/tools/google_calendar/update'
export const googleCalendarCreateTool = createTool
export const googleCalendarDeleteTool = deleteTool
export const googleCalendarFreeBusyTool = freebusyTool
export const googleCalendarGetTool = getTool
export const googleCalendarInstancesTool = instancesTool
export const googleCalendarInviteTool = inviteTool
@@ -22,6 +24,7 @@ export const googleCalendarUpdateTool = updateTool
export const googleCalendarCreateV2Tool = createV2Tool
export const googleCalendarDeleteV2Tool = deleteV2Tool
export const googleCalendarFreeBusyV2Tool = freebusyV2Tool
export const googleCalendarGetV2Tool = getV2Tool
export const googleCalendarInstancesV2Tool = instancesV2Tool
export const googleCalendarInviteV2Tool = inviteV2Tool

View File

@@ -90,6 +90,14 @@ export interface GoogleCalendarInstancesParams extends BaseGoogleCalendarParams
showDeleted?: boolean
}
export interface GoogleCalendarFreeBusyParams {
accessToken: string
calendarIds: string // Comma-separated calendar IDs (e.g., "primary,other@example.com")
timeMin: string // RFC3339 timestamp (e.g., 2025-06-03T00:00:00Z)
timeMax: string // RFC3339 timestamp (e.g., 2025-06-04T00:00:00Z)
timeZone?: string // IANA time zone (e.g., "UTC", "America/New_York")
}
export interface GoogleCalendarListCalendarsParams {
accessToken: string
minAccessRole?: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
@@ -109,6 +117,7 @@ export type GoogleCalendarToolParams =
| GoogleCalendarInviteParams
| GoogleCalendarMoveParams
| GoogleCalendarInstancesParams
| GoogleCalendarFreeBusyParams
| GoogleCalendarListCalendarsParams
interface EventMetadata {
@@ -341,6 +350,36 @@ export interface GoogleCalendarInstancesResponse extends ToolResponse {
}
}
export interface GoogleCalendarFreeBusyResponse extends ToolResponse {
output: {
content: string
metadata: {
timeMin: string
timeMax: string
calendars: Record<
string,
{
busy: Array<{ start: string; end: string }>
errors?: Array<{ domain: string; reason: string }>
}
>
}
}
}
export interface GoogleCalendarApiFreeBusyResponse {
kind: string
timeMin: string
timeMax: string
calendars: Record<
string,
{
busy: Array<{ start: string; end: string }>
errors?: Array<{ domain: string; reason: string }>
}
>
}
export interface GoogleCalendarListCalendarsResponse extends ToolResponse {
output: {
content: string
@@ -373,4 +412,5 @@ export type GoogleCalendarResponse =
| GoogleCalendarDeleteResponse
| GoogleCalendarMoveResponse
| GoogleCalendarInstancesResponse
| GoogleCalendarFreeBusyResponse
| GoogleCalendarListCalendarsResponse

View File

@@ -7,6 +7,8 @@ import { getContentTool } from '@/tools/google_drive/get_content'
import { getFileTool } from '@/tools/google_drive/get_file'
import { listTool } from '@/tools/google_drive/list'
import { listPermissionsTool } from '@/tools/google_drive/list_permissions'
import { moveTool } from '@/tools/google_drive/move'
import { searchTool } from '@/tools/google_drive/search'
import { shareTool } from '@/tools/google_drive/share'
import { trashTool } from '@/tools/google_drive/trash'
import { unshareTool } from '@/tools/google_drive/unshare'
@@ -23,6 +25,8 @@ export const googleDriveGetContentTool = getContentTool
export const googleDriveGetFileTool = getFileTool
export const googleDriveListTool = listTool
export const googleDriveListPermissionsTool = listPermissionsTool
export const googleDriveMoveTool = moveTool
export const googleDriveSearchTool = searchTool
export const googleDriveShareTool = shareTool
export const googleDriveTrashTool = trashTool
export const googleDriveUnshareTool = unshareTool

View File

@@ -0,0 +1,145 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveMoveParams extends GoogleDriveToolParams {
fileId: string
destinationFolderId: string
removeFromCurrent?: boolean
}
interface GoogleDriveMoveResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const moveTool: ToolConfig<GoogleDriveMoveParams, GoogleDriveMoveResponse> = {
id: 'google_drive_move',
name: 'Move Google Drive File',
description: 'Move a file or folder to a different folder in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file or folder to move',
},
destinationFolderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the destination folder',
},
removeFromCurrent: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Whether to remove the file from its current parent folder (default: true). Set to false to add the file to the destination without removing it from the current location.',
},
},
request: {
url: 'https://www.googleapis.com/drive/v3/files',
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
directExecution: async (params) => {
const fileId = params.fileId?.trim()
const destinationFolderId = params.destinationFolderId?.trim()
const removeFromCurrent = params.removeFromCurrent !== false
if (!fileId) {
throw new Error('fileId is required')
}
if (!destinationFolderId) {
throw new Error('destinationFolderId is required')
}
const headers = {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
// Build the PATCH URL with addParents
const url = new URL(`https://www.googleapis.com/drive/v3/files/${fileId}`)
url.searchParams.append('addParents', destinationFolderId)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
if (removeFromCurrent) {
// Fetch current parents so we can remove them
const metadataUrl = new URL(`https://www.googleapis.com/drive/v3/files/${fileId}`)
metadataUrl.searchParams.append('fields', 'parents')
metadataUrl.searchParams.append('supportsAllDrives', 'true')
const metadataResponse = await fetch(metadataUrl.toString(), { headers })
if (!metadataResponse.ok) {
const errorData = await metadataResponse.json()
throw new Error(errorData.error?.message || 'Failed to retrieve file metadata')
}
const metadata = await metadataResponse.json()
if (metadata.parents && metadata.parents.length > 0) {
url.searchParams.append('removeParents', metadata.parents.join(','))
}
}
const response = await fetch(url.toString(), {
method: 'PATCH',
headers,
body: JSON.stringify({}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to move Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The moved file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
owners: { type: 'json', description: 'List of file owners' },
size: { type: 'string', description: 'File size in bytes' },
},
},
},
}

View File

@@ -0,0 +1,145 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveSearchParams extends GoogleDriveToolParams {
query: string
pageSize?: number
pageToken?: string
}
interface GoogleDriveSearchResponse extends ToolResponse {
output: {
files: GoogleDriveFile[]
nextPageToken?: string
}
}
export const searchTool: ToolConfig<GoogleDriveSearchParams, GoogleDriveSearchResponse> = {
id: 'google_drive_search',
name: 'Search Google Drive Files',
description:
'Search for files in Google Drive using advanced query syntax (e.g., fullText contains, mimeType, modifiedTime, etc.)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Google Drive query string using advanced search syntax (e.g., "fullText contains \'budget\'", "mimeType = \'application/pdf\'", "modifiedTime > \'2024-01-01\'")',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of files to return (default: 100)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Token for fetching the next page of results',
},
},
request: {
url: (params) => {
const url = new URL('https://www.googleapis.com/drive/v3/files')
url.searchParams.append('fields', `files(${ALL_FILE_FIELDS}),nextPageToken`)
url.searchParams.append('corpora', 'allDrives')
url.searchParams.append('supportsAllDrives', 'true')
url.searchParams.append('includeItemsFromAllDrives', 'true')
// The query is passed directly as Google Drive query syntax
const conditions = ['trashed = false']
if (params.query?.trim()) {
conditions.push(params.query.trim())
}
url.searchParams.append('q', conditions.join(' and '))
if (params.pageSize) {
url.searchParams.append('pageSize', Number(params.pageSize).toString())
}
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to search Google Drive files')
}
return {
success: true,
output: {
files: data.files || [],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
files: {
type: 'array',
description: 'Array of file metadata objects matching the search query',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
description: { type: 'string', description: 'File description' },
originalFilename: { type: 'string', description: 'Original uploaded filename' },
fullFileExtension: { type: 'string', description: 'Full file extension' },
fileExtension: { type: 'string', description: 'File extension' },
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
shared: { type: 'boolean', description: 'Whether file is shared' },
ownedByMe: { type: 'boolean', description: 'Whether owned by current user' },
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
lastModifyingUser: { type: 'json', description: 'User who last modified the file' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
webContentLink: { type: 'string', description: 'Direct download URL' },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail' },
size: { type: 'string', description: 'File size in bytes' },
parents: { type: 'json', description: 'Parent folder IDs' },
driveId: { type: 'string', description: 'Shared drive ID' },
capabilities: { type: 'json', description: 'User capabilities on file' },
version: { type: 'string', description: 'Version number' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
},
},
}

View File

@@ -0,0 +1,156 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
export interface GoogleSheetsV2DeleteRowsParams {
accessToken: string
spreadsheetId: string
sheetId: number
startIndex: number
endIndex: number
}
export interface GoogleSheetsV2DeleteRowsResponse extends ToolResponse {
output: {
spreadsheetId: string
sheetId: number
deletedRowRange: string
metadata: {
spreadsheetId: string
spreadsheetUrl: string
}
}
}
export const deleteRowsV2Tool: ToolConfig<
GoogleSheetsV2DeleteRowsParams,
GoogleSheetsV2DeleteRowsResponse
> = {
id: 'google_sheets_delete_rows_v2',
name: 'Delete Rows from Google Sheets V2',
description: 'Delete rows from a sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Google Sheets spreadsheet ID',
},
sheetId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description:
'The numeric ID of the sheet/tab (not the sheet name). Use Get Spreadsheet to find sheet IDs.',
},
startIndex: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'The start row index (0-based, inclusive) of the rows to delete',
},
endIndex: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'The end row index (0-based, exclusive) of the rows to delete',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}:batchUpdate`
},
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
if (params.sheetId === undefined || params.sheetId === null) {
throw new Error('Sheet ID is required')
}
if (params.startIndex === undefined || params.startIndex === null) {
throw new Error('Start index is required')
}
if (params.endIndex === undefined || params.endIndex === null) {
throw new Error('End index is required')
}
return {
requests: [
{
deleteDimension: {
range: {
sheetId: params.sheetId,
dimension: 'ROWS',
startIndex: params.startIndex,
endIndex: params.endIndex,
},
},
},
],
}
},
},
transformResponse: async (response: Response, params?: GoogleSheetsV2DeleteRowsParams) => {
await response.json()
const spreadsheetId = params?.spreadsheetId ?? ''
const startIndex = params?.startIndex ?? 0
const endIndex = params?.endIndex ?? 0
return {
success: true,
output: {
spreadsheetId,
sheetId: params?.sheetId ?? 0,
deletedRowRange: `rows ${startIndex} to ${endIndex}`,
metadata: {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
},
},
}
},
outputs: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
sheetId: { type: 'number', description: 'The numeric ID of the sheet' },
deletedRowRange: {
type: 'string',
description: 'Description of the deleted row range',
},
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -0,0 +1,123 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
export interface GoogleSheetsV2DeleteSheetParams {
accessToken: string
spreadsheetId: string
sheetId: number
}
export interface GoogleSheetsV2DeleteSheetResponse extends ToolResponse {
output: {
spreadsheetId: string
deletedSheetId: number
metadata: {
spreadsheetId: string
spreadsheetUrl: string
}
}
}
export const deleteSheetV2Tool: ToolConfig<
GoogleSheetsV2DeleteSheetParams,
GoogleSheetsV2DeleteSheetResponse
> = {
id: 'google_sheets_delete_sheet_v2',
name: 'Delete Sheet V2',
description: 'Delete a sheet/tab from a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Google Sheets spreadsheet ID',
},
sheetId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description:
'The numeric ID of the sheet/tab to delete (not the sheet name). Use Get Spreadsheet to find sheet IDs.',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}:batchUpdate`
},
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
if (params.sheetId === undefined || params.sheetId === null) {
throw new Error('Sheet ID is required')
}
return {
requests: [
{
deleteSheet: {
sheetId: params.sheetId,
},
},
],
}
},
},
transformResponse: async (response: Response, params?: GoogleSheetsV2DeleteSheetParams) => {
await response.json()
const spreadsheetId = params?.spreadsheetId ?? ''
return {
success: true,
output: {
spreadsheetId,
deletedSheetId: params?.sheetId ?? 0,
metadata: {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
},
},
}
},
outputs: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
deletedSheetId: { type: 'number', description: 'The numeric ID of the deleted sheet' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -0,0 +1,86 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
export interface GoogleSheetsV2DeleteSpreadsheetParams {
accessToken: string
spreadsheetId: string
}
export interface GoogleSheetsV2DeleteSpreadsheetResponse extends ToolResponse {
output: {
spreadsheetId: string
deleted: boolean
}
}
export const deleteSpreadsheetV2Tool: ToolConfig<
GoogleSheetsV2DeleteSpreadsheetParams,
GoogleSheetsV2DeleteSpreadsheetResponse
> = {
id: 'google_sheets_delete_spreadsheet_v2',
name: 'Delete Spreadsheet V2',
description: 'Permanently delete a Google Sheets spreadsheet using the Google Drive API',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the Google Sheets spreadsheet to delete',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
return `https://www.googleapis.com/drive/v3/files/${spreadsheetId}`
},
method: 'DELETE',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response, params?: GoogleSheetsV2DeleteSpreadsheetParams) => {
const spreadsheetId = params?.spreadsheetId ?? ''
if (response.status === 204 || response.ok) {
return {
success: true,
output: {
spreadsheetId,
deleted: true,
},
}
}
const data = await response.json()
throw new Error(data.error?.message ?? 'Failed to delete spreadsheet')
},
outputs: {
spreadsheetId: { type: 'string', description: 'The ID of the deleted spreadsheet' },
deleted: { type: 'boolean', description: 'Whether the spreadsheet was successfully deleted' },
},
}

View File

@@ -5,6 +5,9 @@ import { batchUpdateV2Tool } from '@/tools/google_sheets/batch_update'
import { clearV2Tool } from '@/tools/google_sheets/clear'
import { copySheetV2Tool } from '@/tools/google_sheets/copy_sheet'
import { createSpreadsheetV2Tool } from '@/tools/google_sheets/create_spreadsheet'
import { deleteRowsV2Tool } from '@/tools/google_sheets/delete_rows'
import { deleteSheetV2Tool } from '@/tools/google_sheets/delete_sheet'
import { deleteSpreadsheetV2Tool } from '@/tools/google_sheets/delete_spreadsheet'
import { getSpreadsheetV2Tool } from '@/tools/google_sheets/get_spreadsheet'
import { readTool, readV2Tool } from '@/tools/google_sheets/read'
import { updateTool, updateV2Tool } from '@/tools/google_sheets/update'
@@ -28,3 +31,6 @@ export const googleSheetsBatchGetV2Tool = batchGetV2Tool
export const googleSheetsBatchUpdateV2Tool = batchUpdateV2Tool
export const googleSheetsBatchClearV2Tool = batchClearV2Tool
export const googleSheetsCopySheetV2Tool = copySheetV2Tool
export const googleSheetsDeleteRowsV2Tool = deleteRowsV2Tool
export const googleSheetsDeleteSheetV2Tool = deleteSheetV2Tool
export const googleSheetsDeleteSpreadsheetV2Tool = deleteSpreadsheetV2Tool

View File

@@ -1,3 +1,6 @@
import type { GoogleSheetsV2DeleteRowsResponse } from '@/tools/google_sheets/delete_rows'
import type { GoogleSheetsV2DeleteSheetResponse } from '@/tools/google_sheets/delete_sheet'
import type { GoogleSheetsV2DeleteSpreadsheetResponse } from '@/tools/google_sheets/delete_spreadsheet'
import type { ToolResponse } from '@/tools/types'
export interface GoogleSheetsRange {
@@ -146,6 +149,9 @@ export type GoogleSheetsV2Response =
| GoogleSheetsV2BatchUpdateResponse
| GoogleSheetsV2BatchClearResponse
| GoogleSheetsV2CopySheetResponse
| GoogleSheetsV2DeleteRowsResponse
| GoogleSheetsV2DeleteSheetResponse
| GoogleSheetsV2DeleteSpreadsheetResponse
// V2 Clear Types
export interface GoogleSheetsV2ClearParams {

View File

@@ -578,10 +578,18 @@ import {
gmailAddLabelV2Tool,
gmailArchiveTool,
gmailArchiveV2Tool,
gmailCreateLabelV2Tool,
gmailDeleteDraftV2Tool,
gmailDeleteLabelV2Tool,
gmailDeleteTool,
gmailDeleteV2Tool,
gmailDraftTool,
gmailDraftV2Tool,
gmailGetDraftV2Tool,
gmailGetThreadV2Tool,
gmailListDraftsV2Tool,
gmailListLabelsV2Tool,
gmailListThreadsV2Tool,
gmailMarkReadTool,
gmailMarkReadV2Tool,
gmailMarkUnreadTool,
@@ -596,8 +604,10 @@ import {
gmailSearchV2Tool,
gmailSendTool,
gmailSendV2Tool,
gmailTrashThreadV2Tool,
gmailUnarchiveTool,
gmailUnarchiveV2Tool,
gmailUntrashThreadV2Tool,
} from '@/tools/gmail'
import {
gongAggregateActivityTool,
@@ -626,6 +636,8 @@ import {
googleCalendarCreateV2Tool,
googleCalendarDeleteTool,
googleCalendarDeleteV2Tool,
googleCalendarFreeBusyTool,
googleCalendarFreeBusyV2Tool,
googleCalendarGetTool,
googleCalendarGetV2Tool,
googleCalendarInstancesTool,
@@ -654,6 +666,8 @@ import {
googleDriveGetFileTool,
googleDriveListPermissionsTool,
googleDriveListTool,
googleDriveMoveTool,
googleDriveSearchTool,
googleDriveShareTool,
googleDriveTrashTool,
googleDriveUnshareTool,
@@ -714,6 +728,9 @@ import {
googleSheetsClearV2Tool,
googleSheetsCopySheetV2Tool,
googleSheetsCreateSpreadsheetV2Tool,
googleSheetsDeleteRowsV2Tool,
googleSheetsDeleteSheetV2Tool,
googleSheetsDeleteSpreadsheetV2Tool,
googleSheetsGetSpreadsheetV2Tool,
googleSheetsReadTool,
googleSheetsReadV2Tool,
@@ -2472,6 +2489,16 @@ export const tools: Record<string, ToolConfig> = {
gmail_add_label_v2: gmailAddLabelV2Tool,
gmail_remove_label: gmailRemoveLabelTool,
gmail_remove_label_v2: gmailRemoveLabelV2Tool,
gmail_create_label_v2: gmailCreateLabelV2Tool,
gmail_delete_draft_v2: gmailDeleteDraftV2Tool,
gmail_delete_label_v2: gmailDeleteLabelV2Tool,
gmail_get_draft_v2: gmailGetDraftV2Tool,
gmail_get_thread_v2: gmailGetThreadV2Tool,
gmail_list_drafts_v2: gmailListDraftsV2Tool,
gmail_list_labels_v2: gmailListLabelsV2Tool,
gmail_list_threads_v2: gmailListThreadsV2Tool,
gmail_trash_thread_v2: gmailTrashThreadV2Tool,
gmail_untrash_thread_v2: gmailUntrashThreadV2Tool,
whatsapp_send_message: whatsappSendMessageTool,
x_write: xWriteTool,
x_read: xReadTool,
@@ -2868,6 +2895,8 @@ export const tools: Record<string, ToolConfig> = {
google_drive_get_file: googleDriveGetFileTool,
google_drive_list: googleDriveListTool,
google_drive_list_permissions: googleDriveListPermissionsTool,
google_drive_move: googleDriveMoveTool,
google_drive_search: googleDriveSearchTool,
google_drive_share: googleDriveShareTool,
google_drive_trash: googleDriveTrashTool,
google_drive_unshare: googleDriveUnshareTool,
@@ -2909,6 +2938,9 @@ export const tools: Record<string, ToolConfig> = {
google_sheets_batch_update_v2: googleSheetsBatchUpdateV2Tool,
google_sheets_batch_clear_v2: googleSheetsBatchClearV2Tool,
google_sheets_copy_sheet_v2: googleSheetsCopySheetV2Tool,
google_sheets_delete_rows_v2: googleSheetsDeleteRowsV2Tool,
google_sheets_delete_sheet_v2: googleSheetsDeleteSheetV2Tool,
google_sheets_delete_spreadsheet_v2: googleSheetsDeleteSpreadsheetV2Tool,
google_slides_read: googleSlidesReadTool,
google_slides_write: googleSlidesWriteTool,
google_slides_create: googleSlidesCreateTool,
@@ -3508,6 +3540,8 @@ export const tools: Record<string, ToolConfig> = {
google_calendar_quick_add_v2: googleCalendarQuickAddV2Tool,
google_calendar_update: googleCalendarUpdateTool,
google_calendar_update_v2: googleCalendarUpdateV2Tool,
google_calendar_freebusy: googleCalendarFreeBusyTool,
google_calendar_freebusy_v2: googleCalendarFreeBusyV2Tool,
google_forms_get_responses: googleFormsGetResponsesTool,
google_forms_get_form: googleFormsGetFormTool,
google_forms_create_form: googleFormsCreateFormTool,