fix(tools): fixed tool outputs (#2325)

* fix for asana and apify

* fixed onedrive

* fixed confluence error throwing and added upload file

* fixed google vault tag dropdown and output

* fix google group tag dropdown, var reference

* fixed hubspot output

* fixed pipedrive output

* removed comments

* removed more comments

* consolidated file utils

* fixed hubspot json schema

* fix hubspot search tools

* minor change
This commit is contained in:
Adam Gough
2025-12-11 18:24:56 -08:00
committed by GitHub
parent 855e2c418c
commit 030ae5cc0a
85 changed files with 1184 additions and 958 deletions

View File

@@ -54,7 +54,6 @@ Führe einen APIFY-Aktor synchron aus und erhalte Ergebnisse (maximal 5 Minuten)
| `success` | boolean | Ob die Aktor-Ausführung erfolgreich war |
| `runId` | string | APIFY-Ausführungs-ID |
| `status` | string | Ausführungsstatus \(SUCCEEDED, FAILED, usw.\) |
| `datasetId` | string | Dataset-ID mit Ergebnissen |
| `items` | array | Dataset-Elemente \(falls abgeschlossen\) |
### `apify_run_actor_async`

View File

@@ -57,7 +57,6 @@ Run an APIFY actor synchronously and get results (max 5 minutes)
| `success` | boolean | Whether the actor run succeeded |
| `runId` | string | APIFY run ID |
| `status` | string | Run status \(SUCCEEDED, FAILED, etc.\) |
| `datasetId` | string | Dataset ID containing results |
| `items` | array | Dataset items \(if completed\) |
### `apify_run_actor_async`

View File

@@ -54,7 +54,6 @@ Ejecuta un actor de APIFY de forma sincrónica y obtén resultados (máximo 5 mi
| `success` | boolean | Si la ejecución del actor tuvo éxito |
| `runId` | string | ID de ejecución de APIFY |
| `status` | string | Estado de la ejecución \(SUCCEEDED, FAILED, etc.\) |
| `datasetId` | string | ID del conjunto de datos que contiene los resultados |
| `items` | array | Elementos del conjunto de datos \(si se completó\) |
### `apify_run_actor_async`

View File

@@ -54,7 +54,6 @@ Exécuter un acteur APIFY de manière synchrone et obtenir les résultats (maxim
| `success` | booléen | Indique si l'exécution de l'acteur a réussi |
| `runId` | chaîne | ID d'exécution APIFY |
| `status` | chaîne | Statut d'exécution \(SUCCEEDED, FAILED, etc.\) |
| `datasetId` | chaîne | ID du jeu de données contenant les résultats |
| `items` | tableau | Éléments du jeu de données \(si terminé\) |
### `apify_run_actor_async`

View File

@@ -54,7 +54,6 @@ APIPYアクターを同期的に実行して結果を取得最大5分
| `success` | boolean | アクター実行が成功したかどうか |
| `runId` | string | APIFY実行ID |
| `status` | string | 実行ステータスSUCCEEDED、FAILEDなど |
| `datasetId` | string | 結果を含むデータセットID |
| `items` | array | データセット項目(完了した場合) |
### `apify_run_actor_async`

View File

@@ -54,7 +54,6 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| `success` | boolean | actor 运行是否成功 |
| `runId` | string | APIFY 运行 ID |
| `status` | string | 运行状态 \(SUCCEEDED, FAILED 等\) |
| `datasetId` | string | 包含结果的数据集 ID |
| `items` | array | 数据集条目 \(如果已完成\) |
### `apify_run_actor_async`

View File

@@ -15,6 +15,7 @@ import {
extractCleanFilename,
extractStorageKey,
extractWorkspaceIdFromExecutionKey,
getMimeTypeFromExtension,
getViewerUrl,
inferContextFromKey,
} from '@/lib/uploads/utils/file-utils'
@@ -44,36 +45,6 @@ interface ParseResult {
}
}
const fileTypeMap: Record<string, string> = {
// Text formats
txt: 'text/plain',
csv: 'text/csv',
json: 'application/json',
xml: 'application/xml',
md: 'text/markdown',
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
ts: 'application/typescript',
// Document formats
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
// Spreadsheet formats
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// Presentation formats
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// Image formats
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
// Archive formats
zip: 'application/zip',
}
/**
* Main API route handler
*/
@@ -382,7 +353,8 @@ async function handleExternalUrl(
})
} else {
const { uploadWorkspaceFile } = await import('@/lib/uploads/contexts/workspace')
const mimeType = response.headers.get('content-type') || getMimeType(extension)
const mimeType =
response.headers.get('content-type') || getMimeTypeFromExtension(extension)
await uploadWorkspaceFile(workspaceId, userId, buffer, filename, mimeType)
logger.info(`Saved URL file to workspace storage: ${filename}`)
}
@@ -574,7 +546,7 @@ async function handleLocalFile(
content: result.content,
filePath,
metadata: {
fileType: fileType || getMimeType(extension),
fileType: fileType || getMimeTypeFromExtension(extension),
size: stats.size,
hash,
processingTime: 0,
@@ -709,7 +681,7 @@ async function handleGenericTextBuffer(
content: result.content,
filePath: originalPath || filename,
metadata: {
fileType: fileType || getMimeType(extension),
fileType: fileType || getMimeTypeFromExtension(extension),
size: fileBuffer.length,
hash: createHash('md5').update(fileBuffer).digest('hex'),
processingTime: 0,
@@ -727,7 +699,7 @@ async function handleGenericTextBuffer(
content,
filePath: originalPath || filename,
metadata: {
fileType: fileType || getMimeType(extension),
fileType: fileType || getMimeTypeFromExtension(extension),
size: fileBuffer.length,
hash: createHash('md5').update(fileBuffer).digest('hex'),
processingTime: 0,
@@ -768,7 +740,7 @@ function handleGenericBuffer(
content,
filePath: filename,
metadata: {
fileType: fileType || getMimeType(extension),
fileType: fileType || getMimeTypeFromExtension(extension),
size: fileBuffer.length,
hash: createHash('md5').update(fileBuffer).digest('hex'),
processingTime: 0,
@@ -791,13 +763,6 @@ async function parseBufferAsPdf(buffer: Buffer) {
}
}
/**
* Get MIME type from file extension
*/
function getMimeType(extension: string): string {
return fileTypeMap[extension] || 'application/octet-stream'
}
/**
* Format bytes to human readable size
*/

View File

@@ -86,18 +86,16 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
gid: story.gid,
text: story.text || '',
created_at: story.created_at,
created_by: story.created_by
? {
gid: story.created_by.gid,
name: story.created_by.name,
}
: undefined,
},
ts: new Date().toISOString(),
gid: story.gid,
text: story.text || '',
created_at: story.created_at,
created_by: story.created_by
? {
gid: story.created_by.gid,
name: story.created_by.name,
}
: undefined,
})
} catch (error) {
logger.error('Error processing request:', error)

View File

@@ -99,15 +99,13 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
gid: task.gid,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
created_at: task.created_at,
permalink_url: task.permalink_url,
},
ts: new Date().toISOString(),
gid: task.gid,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
created_at: task.created_at,
permalink_url: task.permalink_url,
})
} catch (error: any) {
logger.error('Error creating Asana task:', {

View File

@@ -73,14 +73,12 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
projects: projects.map((project: any) => ({
gid: project.gid,
name: project.name,
resource_type: project.resource_type,
})),
},
ts: new Date().toISOString(),
projects: projects.map((project: any) => ({
gid: project.gid,
name: project.name,
resource_type: project.resource_type,
})),
})
} catch (error) {
logger.error('Error processing request:', error)

View File

@@ -69,31 +69,29 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
},
ts: new Date().toISOString(),
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
})
}
@@ -180,34 +178,32 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
tasks: tasks.map((task: any) => ({
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
})),
next_page: result.next_page,
},
ts: new Date().toISOString(),
tasks: tasks.map((task: any) => ({
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
})),
next_page: result.next_page,
})
} catch (error) {
logger.error('Error processing request:', error)

View File

@@ -96,34 +96,32 @@ export async function POST(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
tasks: tasks.map((task: any) => ({
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
})),
next_page: result.next_page,
},
ts: new Date().toISOString(),
tasks: tasks.map((task: any) => ({
gid: task.gid,
resource_type: task.resource_type,
resource_subtype: task.resource_subtype,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
assignee: task.assignee
? {
gid: task.assignee.gid,
name: task.assignee.name,
}
: undefined,
created_by: task.created_by
? {
gid: task.created_by.gid,
resource_type: task.created_by.resource_type,
name: task.created_by.name,
}
: undefined,
due_on: task.due_on || undefined,
created_at: task.created_at,
modified_at: task.modified_at,
})),
next_page: result.next_page,
})
} catch (error) {
logger.error('Error processing request:', error)

View File

@@ -99,14 +99,12 @@ export async function PUT(request: Request) {
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
gid: task.gid,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
modified_at: task.modified_at,
},
ts: new Date().toISOString(),
gid: task.gid,
name: task.name,
notes: task.notes || '',
completed: task.completed || false,
modified_at: task.modified_at,
})
} catch (error: any) {
logger.error('Error updating Asana task:', {

View File

@@ -31,6 +31,16 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Space ID is required' }, { status: 400 })
}
if (!/^\d+$/.test(String(spaceId))) {
return NextResponse.json(
{
error:
'Invalid Space ID. The Space ID must be a numeric value, not the space key from the URL. Use the "list" operation to get all spaces with their numeric IDs.',
},
{ status: 400 }
)
}
if (!title) {
return NextResponse.json({ error: 'Title is required' }, { status: 400 })
}
@@ -91,10 +101,24 @@ export async function POST(request: Request) {
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
})
const errorMessage =
errorData?.message ||
(errorData?.errors && JSON.stringify(errorData.errors)) ||
`Failed to create Confluence page (${response.status})`
let errorMessage = `Failed to create Confluence page (${response.status})`
if (errorData?.message) {
errorMessage = errorData.message
} else if (errorData?.errors && Array.isArray(errorData.errors)) {
const firstError = errorData.errors[0]
if (firstError?.title) {
if (firstError.title.includes("'spaceId'") && firstError.title.includes('Long')) {
errorMessage =
'Invalid Space ID. Use the list spaces operation to find valid space IDs.'
} else {
errorMessage = firstError.title
}
} else {
errorMessage = JSON.stringify(errorData.errors)
}
}
return NextResponse.json({ error: errorMessage }, { status: response.status })
}

View File

@@ -0,0 +1,135 @@
import { type NextRequest, NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
import { createLogger } from '@/lib/logs/console/logger'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceUploadAttachmentAPI')
export const dynamic = 'force-dynamic'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { domain, accessToken, cloudId: providedCloudId, pageId, file, fileName, comment } = body
if (!domain) {
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
}
if (!accessToken) {
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
}
if (!pageId) {
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
}
if (!file) {
return NextResponse.json({ error: 'File is required' }, { status: 400 })
}
const pageIdValidation = validateAlphanumericId(pageId, 'pageId', 255)
if (!pageIdValidation.isValid) {
return NextResponse.json({ error: pageIdValidation.error }, { status: 400 })
}
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
let fileToProcess = file
if (Array.isArray(file)) {
if (file.length === 0) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
}
fileToProcess = file[0]
}
let userFile
try {
userFile = processSingleFileToUserFile(fileToProcess, 'confluence-upload', logger)
} catch (error) {
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to process file' },
{ status: 400 }
)
}
let fileBuffer: Buffer
try {
fileBuffer = await downloadFileFromStorage(userFile, 'confluence-upload', logger)
} catch (error) {
logger.error('Failed to download file from storage:', error)
return NextResponse.json(
{
error: `Failed to download file: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
{ status: 500 }
)
}
const uploadFileName = fileName || userFile.name || 'attachment'
const mimeType = userFile.type || 'application/octet-stream'
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/rest/api/content/${pageId}/child/attachment`
const formData = new FormData()
const blob = new Blob([new Uint8Array(fileBuffer)], { type: mimeType })
formData.append('file', blob, uploadFileName)
if (comment) {
formData.append('comment', comment)
}
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'X-Atlassian-Token': 'nocheck',
},
body: formData,
})
if (!response.ok) {
const errorData = await response.json().catch(() => null)
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
})
let errorMessage = `Failed to upload attachment to Confluence (${response.status})`
if (errorData?.message) {
errorMessage = errorData.message
} else if (errorData?.errorMessage) {
errorMessage = errorData.errorMessage
}
return NextResponse.json({ error: errorMessage }, { status: response.status })
}
const data = await response.json()
const attachment = data.results?.[0] || data
return NextResponse.json({
attachmentId: attachment.id,
title: attachment.title,
fileSize: attachment.extensions?.fileSize || 0,
mediaType: attachment.extensions?.mediaType || mimeType,
downloadUrl: attachment._links?.download || '',
pageId: pageId,
})
} catch (error) {
logger.error('Error uploading Confluence attachment:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
)
}
}

View File

@@ -4,7 +4,10 @@ import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import {
getExtensionFromMimeType,
processSingleFileToUserFile,
} from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
import { normalizeExcelValues } from '@/tools/onedrive/utils'
@@ -27,9 +30,8 @@ const OneDriveUploadSchema = z.object({
fileName: z.string().min(1, 'File name is required'),
file: z.any().optional(), // UserFile object (optional for blank Excel creation)
folderId: z.string().optional().nullable(),
mimeType: z.string().optional(),
// Optional Excel write-after-create inputs
values: ExcelValuesSchema.optional(),
mimeType: z.string().nullish(), // Accept string, null, or undefined
values: ExcelValuesSchema.optional().nullable(),
})
export async function POST(request: NextRequest) {
@@ -149,9 +151,17 @@ export async function POST(request: NextRequest) {
)
}
// Ensure file name has correct extension for Excel files
// Ensure file name has an appropriate extension
let fileName = validatedData.fileName
if (isExcelCreation && !fileName.endsWith('.xlsx')) {
const hasExtension = fileName.includes('.') && fileName.lastIndexOf('.') > 0
if (!hasExtension) {
const extension = getExtensionFromMimeType(mimeType)
if (extension) {
fileName = `${fileName}.${extension}`
logger.info(`[${requestId}] Added extension to filename: ${fileName}`)
}
} else if (isExcelCreation && !fileName.endsWith('.xlsx')) {
fileName = `${fileName.replace(/\.[^.]*$/, '')}.xlsx`
}

View File

@@ -1,4 +1,8 @@
import type React from 'react'
import {
SUPPORTED_AUDIO_EXTENSIONS,
SUPPORTED_VIDEO_EXTENSIONS,
} from '@/lib/uploads/utils/validation'
interface IconProps {
className?: string
@@ -223,13 +227,19 @@ export const DefaultFileIcon: React.FC<IconProps> = ({ className = 'w-6 h-6' })
export function getDocumentIcon(mimeType: string, filename: string): React.FC<IconProps> {
const extension = filename.split('.').pop()?.toLowerCase()
const audioExtensions = ['mp3', 'm4a', 'wav', 'webm', 'ogg', 'flac', 'aac', 'opus']
if (mimeType.startsWith('audio/') || (extension && audioExtensions.includes(extension))) {
if (
mimeType.startsWith('audio/') ||
(extension &&
SUPPORTED_AUDIO_EXTENSIONS.includes(extension as (typeof SUPPORTED_AUDIO_EXTENSIONS)[number]))
) {
return AudioIcon
}
const videoExtensions = ['mp4', 'mov', 'avi', 'mkv']
if (mimeType.startsWith('video/') || (extension && videoExtensions.includes(extension))) {
if (
mimeType.startsWith('video/') ||
(extension &&
SUPPORTED_VIDEO_EXTENSIONS.includes(extension as (typeof SUPPORTED_VIDEO_EXTENSIONS)[number]))
) {
return VideoIcon
}

View File

@@ -207,6 +207,18 @@ const generateOutputPaths = (outputs: Record<string, any>, prefix = ''): string[
} else if (typeof value === 'object' && value !== null) {
if ('type' in value && typeof value.type === 'string') {
paths.push(currentPath)
if (value.type === 'object' && value.properties) {
paths.push(...generateOutputPaths(value.properties, currentPath))
} else if (value.type === 'array' && value.items?.properties) {
paths.push(...generateOutputPaths(value.items.properties, currentPath))
} else if (
value.type === 'array' &&
value.items &&
typeof value.items === 'object' &&
!('type' in value.items)
) {
paths.push(...generateOutputPaths(value.items, currentPath))
}
} else {
const subPaths = generateOutputPaths(value, currentPath)
paths.push(...subPaths)

View File

@@ -287,6 +287,19 @@ export const AsanaBlock: BlockConfig<AsanaResponse> = {
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: { type: 'string', description: 'Operation result (JSON)' },
ts: { type: 'string', description: 'Timestamp of the response' },
gid: { type: 'string', description: 'Resource globally unique identifier' },
name: { type: 'string', description: 'Resource name' },
notes: { type: 'string', description: 'Task notes or description' },
completed: { type: 'boolean', description: 'Whether the task is completed' },
text: { type: 'string', description: 'Comment text content' },
assignee: { type: 'json', description: 'Assignee details (gid, name)' },
created_by: { type: 'json', description: 'Creator details (gid, name)' },
due_on: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
created_at: { type: 'string', description: 'Creation timestamp' },
modified_at: { type: 'string', description: 'Last modified timestamp' },
permalink_url: { type: 'string', description: 'URL to the resource in Asana' },
tasks: { type: 'json', description: 'Array of tasks' },
projects: { type: 'json', description: 'Array of projects' },
},
}

View File

@@ -29,6 +29,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
{ label: 'List Comments', id: 'list_comments' },
{ label: 'Update Comment', id: 'update_comment' },
{ label: 'Delete Comment', id: 'delete_comment' },
{ label: 'Upload Attachment', id: 'upload_attachment' },
{ label: 'List Attachments', id: 'list_attachments' },
{ label: 'Delete Attachment', id: 'delete_attachment' },
{ label: 'List Labels', id: 'list_labels' },
@@ -155,6 +156,28 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
required: true,
condition: { field: 'operation', value: 'delete_attachment' },
},
{
id: 'attachmentFile',
title: 'File',
type: 'file-upload',
placeholder: 'Select file to upload',
required: true,
condition: { field: 'operation', value: 'upload_attachment' },
},
{
id: 'attachmentFileName',
title: 'File Name',
type: 'short-input',
placeholder: 'Optional custom file name',
condition: { field: 'operation', value: 'upload_attachment' },
},
{
id: 'attachmentComment',
title: 'Comment',
type: 'short-input',
placeholder: 'Optional comment for the attachment',
condition: { field: 'operation', value: 'upload_attachment' },
},
{
id: 'labelName',
title: 'Label Name',
@@ -185,6 +208,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
'confluence_list_comments',
'confluence_update_comment',
'confluence_delete_comment',
'confluence_upload_attachment',
'confluence_list_attachments',
'confluence_delete_attachment',
'confluence_list_labels',
@@ -212,6 +236,8 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
return 'confluence_update_comment'
case 'delete_comment':
return 'confluence_delete_comment'
case 'upload_attachment':
return 'confluence_upload_attachment'
case 'list_attachments':
return 'confluence_list_attachments'
case 'delete_attachment':
@@ -227,11 +253,19 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
}
},
params: (params) => {
const { credential, pageId, manualPageId, operation, ...rest } = params
const {
credential,
pageId,
manualPageId,
operation,
attachmentFile,
attachmentFileName,
attachmentComment,
...rest
} = params
const effectivePageId = (pageId || manualPageId || '').trim()
// Operations that require pageId
const requiresPageId = [
'read',
'update',
@@ -240,9 +274,9 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
'list_comments',
'list_attachments',
'list_labels',
'upload_attachment',
]
// Operations that require spaceId
const requiresSpaceId = ['create', 'get_space']
if (requiresPageId.includes(operation) && !effectivePageId) {
@@ -253,6 +287,18 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
throw new Error('Space ID is required for this operation.')
}
if (operation === 'upload_attachment') {
return {
credential,
pageId: effectivePageId,
operation,
file: attachmentFile,
fileName: attachmentFileName,
comment: attachmentComment,
...rest,
}
}
return {
credential,
pageId: effectivePageId || undefined,
@@ -276,6 +322,9 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
comment: { type: 'string', description: 'Comment text' },
commentId: { type: 'string', description: 'Comment identifier' },
attachmentId: { type: 'string', description: 'Attachment identifier' },
attachmentFile: { type: 'json', description: 'File to upload as attachment' },
attachmentFileName: { type: 'string', description: 'Custom file name for attachment' },
attachmentComment: { type: 'string', description: 'Comment for the attachment' },
labelName: { type: 'string', description: 'Label name' },
limit: { type: 'number', description: 'Maximum number of results' },
},
@@ -297,6 +346,9 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
spaces: { type: 'array', description: 'List of spaces' },
commentId: { type: 'string', description: 'Comment identifier' },
attachmentId: { type: 'string', description: 'Attachment identifier' },
fileSize: { type: 'number', description: 'Attachment file size in bytes' },
mediaType: { type: 'string', description: 'Attachment MIME type' },
downloadUrl: { type: 'string', description: 'Attachment download URL' },
labelName: { type: 'string', description: 'Label name' },
spaceId: { type: 'string', description: 'Space identifier' },
name: { type: 'string', description: 'Space name' },

View File

@@ -312,6 +312,12 @@ export const GoogleGroupsBlock: BlockConfig = {
roles: { type: 'string', description: 'Filter by roles for list members' },
},
outputs: {
output: { type: 'json', description: 'Google Groups API response data' },
groups: { type: 'json', description: 'Array of group objects (for list_groups)' },
group: { type: 'json', description: 'Single group object (for get/create/update_group)' },
members: { type: 'json', description: 'Array of member objects (for list_members)' },
member: { type: 'json', description: 'Single member object (for get/add/update_member)' },
isMember: { type: 'boolean', description: 'Membership check result (for has_member)' },
message: { type: 'string', description: 'Success message (for delete/remove operations)' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -257,9 +257,16 @@ export const GoogleVaultBlock: BlockConfig = {
description: { type: 'string', description: 'Matter description' },
},
outputs: {
// Common outputs
output: { type: 'json', description: 'Vault API response data' },
// Download export file output
matters: { type: 'json', description: 'Array of matter objects (for list_matters)' },
exports: { type: 'json', description: 'Array of export objects (for list_matters_export)' },
holds: { type: 'json', description: 'Array of hold objects (for list_matters_holds)' },
matter: { type: 'json', description: 'Created matter object (for create_matters)' },
export: { type: 'json', description: 'Created export object (for create_matters_export)' },
hold: { type: 'json', description: 'Created hold object (for create_matters_holds)' },
file: { type: 'json', description: 'Downloaded export file (UserFile) from execution files' },
nextPageToken: {
type: 'string',
description: 'Token for fetching next page of results (for list operations)',
},
},
}

View File

@@ -72,21 +72,37 @@ export const HubSpotBlock: BlockConfig<HubSpotResponse> = {
id: 'contactId',
title: 'Contact ID or Email',
type: 'short-input',
placeholder: 'Optional - Leave empty to list all contacts',
condition: { field: 'operation', value: ['get_contacts', 'update_contact'] },
placeholder: 'Leave empty to list all contacts',
condition: { field: 'operation', value: 'get_contacts' },
},
{
id: 'contactId',
title: 'Contact ID or Email',
type: 'short-input',
placeholder: 'Numeric ID, or email (requires ID Property below)',
condition: { field: 'operation', value: 'update_contact' },
required: true,
},
{
id: 'companyId',
title: 'Company ID or Domain',
type: 'short-input',
placeholder: 'Optional - Leave empty to list all companies',
condition: { field: 'operation', value: ['get_companies', 'update_company'] },
placeholder: 'Leave empty to list all companies',
condition: { field: 'operation', value: 'get_companies' },
},
{
id: 'companyId',
title: 'Company ID or Domain',
type: 'short-input',
placeholder: 'Numeric ID, or domain (requires ID Property below)',
condition: { field: 'operation', value: 'update_company' },
required: true,
},
{
id: 'idProperty',
title: 'ID Property',
type: 'short-input',
placeholder: 'Optional - e.g., "email" for contacts, "domain" for companies',
placeholder: 'Required if using email/domain (e.g., "email" or "domain")',
condition: {
field: 'operation',
value: ['get_contacts', 'update_contact', 'get_companies', 'update_company'],
@@ -822,33 +838,48 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no
credential,
}
if (propertiesToSet) {
const createUpdateOps = [
'create_contact',
'update_contact',
'create_company',
'update_company',
]
if (propertiesToSet && createUpdateOps.includes(operation as string)) {
cleanParams.properties = propertiesToSet
}
if (properties && !searchProperties) {
const getListOps = ['get_contacts', 'get_companies', 'get_deals']
if (properties && !searchProperties && getListOps.includes(operation as string)) {
cleanParams.properties = properties
}
if (searchProperties) {
const searchOps = ['search_contacts', 'search_companies']
if (searchProperties && searchOps.includes(operation as string)) {
cleanParams.properties = searchProperties
}
if (filterGroups) {
if (filterGroups && searchOps.includes(operation as string)) {
cleanParams.filterGroups = filterGroups
}
if (sorts) {
if (sorts && searchOps.includes(operation as string)) {
cleanParams.sorts = sorts
}
if (associations) {
if (associations && ['create_contact', 'create_company'].includes(operation as string)) {
cleanParams.associations = associations
}
// Add other params
const excludeKeys = [
'propertiesToSet',
'properties',
'searchProperties',
'filterGroups',
'sorts',
'associations',
]
Object.entries(rest).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
if (value !== undefined && value !== null && value !== '' && !excludeKeys.includes(key)) {
cleanParams[key] = value
}
})

View File

@@ -214,6 +214,75 @@ export function getMimeTypeFromExtension(extension: string): string {
return extensionMimeMap[extension.toLowerCase()] || 'application/octet-stream'
}
/**
* Get file extension from MIME type
* @param mimeType - MIME type string
* @returns File extension without dot, or null if not found
*/
export function getExtensionFromMimeType(mimeType: string): string | null {
const mimeToExtension: Record<string, string> = {
// Images
'image/jpeg': 'jpg',
'image/jpg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'image/svg+xml': 'svg',
// Documents
'application/pdf': 'pdf',
'text/plain': 'txt',
'text/csv': 'csv',
'application/json': 'json',
'application/xml': 'xml',
'text/xml': 'xml',
'text/html': 'html',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
'application/msword': 'doc',
'application/vnd.ms-excel': 'xls',
'application/vnd.ms-powerpoint': 'ppt',
'text/markdown': 'md',
'application/rtf': 'rtf',
// Audio
'audio/mpeg': 'mp3',
'audio/mp3': 'mp3',
'audio/mp4': 'm4a',
'audio/x-m4a': 'm4a',
'audio/m4a': 'm4a',
'audio/wav': 'wav',
'audio/wave': 'wav',
'audio/x-wav': 'wav',
'audio/webm': 'webm',
'audio/ogg': 'ogg',
'audio/vorbis': 'ogg',
'audio/flac': 'flac',
'audio/x-flac': 'flac',
'audio/aac': 'aac',
'audio/x-aac': 'aac',
'audio/opus': 'opus',
// Video
'video/mp4': 'mp4',
'video/mpeg': 'mpg',
'video/quicktime': 'mov',
'video/x-quicktime': 'mov',
'video/x-msvideo': 'avi',
'video/avi': 'avi',
'video/x-matroska': 'mkv',
'video/webm': 'webm',
// Archives
'application/zip': 'zip',
'application/x-zip-compressed': 'zip',
'application/gzip': 'gz',
}
return mimeToExtension[mimeType.toLowerCase()] || null
}
/**
* Format bytes to human-readable file size
* @param bytes - File size in bytes

View File

@@ -101,7 +101,6 @@ export const apifyRunActorSyncTool: ToolConfig<RunActorParams, RunActorResult> =
success: { type: 'boolean', description: 'Whether the actor run succeeded' },
runId: { type: 'string', description: 'APIFY run ID' },
status: { type: 'string', description: 'Run status (SUCCEEDED, FAILED, etc.)' },
datasetId: { type: 'string', description: 'Dataset ID containing results' },
items: { type: 'array', description: 'Dataset items (if completed)' },
},
}

View File

@@ -52,31 +52,33 @@ export const asanaAddCommentTool: ToolConfig<AsanaAddCommentParams, AsanaAddComm
if (!responseText) {
return {
success: false,
output: {},
error: 'Empty response from Asana',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || null,
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
gid: { type: 'string', description: 'Comment globally unique identifier' },
text: { type: 'string', description: 'Comment text content' },
created_at: { type: 'string', description: 'Comment creation timestamp' },
created_by: {
type: 'object',
description: 'Comment details including gid, text, created timestamp, and author',
description: 'Comment author details',
properties: {
gid: { type: 'string', description: 'Author GID' },
name: { type: 'string', description: 'Author name' },
},
},
},
}

View File

@@ -86,34 +86,22 @@ export const asanaCreateTaskTool: ToolConfig<AsanaCreateTaskParams, AsanaCreateT
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
gid: 'unknown',
name: 'Task creation failed',
notes: '',
completed: false,
created_at: new Date().toISOString(),
permalink_url: '',
},
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'Created task details with timestamp, gid, name, notes, and permalink',
},
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
gid: { type: 'string', description: 'Task globally unique identifier' },
name: { type: 'string', description: 'Task name' },
notes: { type: 'string', description: 'Task notes or description' },
completed: { type: 'boolean', description: 'Whether the task is completed' },
created_at: { type: 'string', description: 'Task creation timestamp' },
permalink_url: { type: 'string', description: 'URL to the task in Asana' },
},
}

View File

@@ -45,31 +45,34 @@ export const asanaGetProjectsTool: ToolConfig<AsanaGetProjectsParams, AsanaGetPr
if (!responseText) {
return {
success: false,
output: {},
error: 'Empty response from Asana',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || { ts: new Date().toISOString(), projects: [] },
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'List of projects with their gid, name, and resource type',
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
projects: {
type: 'array',
description: 'Array of projects',
items: {
type: 'object',
properties: {
gid: { type: 'string', description: 'Project GID' },
name: { type: 'string', description: 'Project name' },
resource_type: { type: 'string', description: 'Resource type (project)' },
},
},
},
},
}

View File

@@ -67,32 +67,59 @@ export const asanaGetTaskTool: ToolConfig<AsanaGetTaskParams, AsanaGetTaskRespon
if (!responseText) {
return {
success: false,
output: {},
error: 'Empty response from Asana',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || null,
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
gid: { type: 'string', description: 'Task globally unique identifier' },
resource_type: { type: 'string', description: 'Resource type (task)' },
resource_subtype: { type: 'string', description: 'Resource subtype' },
name: { type: 'string', description: 'Task name' },
notes: { type: 'string', description: 'Task notes or description' },
completed: { type: 'boolean', description: 'Whether the task is completed' },
assignee: {
type: 'object',
description:
'Single task details or array of tasks, depending on whether taskGid was provided',
description: 'Assignee details',
properties: {
gid: { type: 'string', description: 'Assignee GID' },
name: { type: 'string', description: 'Assignee name' },
},
},
created_by: {
type: 'object',
description: 'Creator details',
properties: {
gid: { type: 'string', description: 'Creator GID' },
name: { type: 'string', description: 'Creator name' },
},
},
due_on: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
created_at: { type: 'string', description: 'Task creation timestamp' },
modified_at: { type: 'string', description: 'Task last modified timestamp' },
tasks: {
type: 'array',
description: 'Array of tasks (when fetching multiple)',
items: {
type: 'object',
properties: {
gid: { type: 'string', description: 'Task GID' },
name: { type: 'string', description: 'Task name' },
completed: { type: 'boolean', description: 'Completion status' },
},
},
},
},
}

View File

@@ -73,31 +73,57 @@ export const asanaSearchTasksTool: ToolConfig<AsanaSearchTasksParams, AsanaSearc
if (!responseText) {
return {
success: false,
output: {},
error: 'Empty response from Asana',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || { ts: new Date().toISOString(), tasks: [] },
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
tasks: {
type: 'array',
description: 'Array of matching tasks',
items: {
type: 'object',
properties: {
gid: { type: 'string', description: 'Task GID' },
resource_type: { type: 'string', description: 'Resource type' },
resource_subtype: { type: 'string', description: 'Resource subtype' },
name: { type: 'string', description: 'Task name' },
notes: { type: 'string', description: 'Task notes' },
completed: { type: 'boolean', description: 'Completion status' },
assignee: {
type: 'object',
description: 'Assignee details',
properties: {
gid: { type: 'string', description: 'Assignee GID' },
name: { type: 'string', description: 'Assignee name' },
},
},
due_on: { type: 'string', description: 'Due date' },
created_at: { type: 'string', description: 'Creation timestamp' },
modified_at: { type: 'string', description: 'Modified timestamp' },
},
},
},
output: {
next_page: {
type: 'object',
description: 'List of tasks matching the search criteria',
description: 'Pagination info',
properties: {
offset: { type: 'string', description: 'Offset token' },
path: { type: 'string', description: 'API path' },
uri: { type: 'string', description: 'Full URI' },
},
},
},
}

View File

@@ -9,56 +9,52 @@ export interface AsanaGetTaskParams {
}
export interface AsanaGetTaskResponse extends ToolResponse {
output:
| {
ts: string
output: {
ts: string
gid?: string
resource_type?: string
resource_subtype?: string
name?: string
notes?: string
completed?: boolean
assignee?: {
gid: string
name: string
}
created_by?: {
gid: string
resource_type: string
name: string
}
due_on?: string
created_at?: string
modified_at?: string
tasks?: Array<{
gid: string
resource_type: string
resource_subtype: string
name: string
notes?: string
completed: boolean
assignee?: {
gid: string
name: string
}
created_by?: {
gid: string
resource_type: string
resource_subtype: string
name: string
notes: string
completed: boolean
assignee?: {
gid: string
name: string
}
created_by?: {
gid: string
resource_type: string
name: string
}
due_on?: string
created_at: string
modified_at: string
}
| {
ts: string
tasks: Array<{
gid: string
resource_type: string
resource_subtype: string
name: string
notes?: string
completed: boolean
assignee?: {
gid: string
name: string
}
created_by?: {
gid: string
resource_type: string
name: string
}
due_on?: string
created_at: string
modified_at: string
}>
next_page?: {
offset: string
path: string
uri: string
}
}
due_on?: string
created_at: string
modified_at: string
}>
next_page?: {
offset: string
path: string
uri: string
}
}
}
export interface AsanaCreateTaskParams {

View File

@@ -92,33 +92,21 @@ export const asanaUpdateTaskTool: ToolConfig<AsanaUpdateTaskParams, AsanaUpdateT
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
const { success, error, ...output } = data
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
gid: 'unknown',
name: 'Task update failed',
notes: '',
completed: false,
modified_at: new Date().toISOString(),
},
error: data.error,
success: success ?? true,
output,
error,
}
},
outputs: {
success: {
type: 'boolean',
description: 'Operation success status',
},
output: {
type: 'object',
description: 'Updated task details with timestamp, gid, name, notes, and modified timestamp',
},
success: { type: 'boolean', description: 'Operation success status' },
ts: { type: 'string', description: 'Timestamp of the response' },
gid: { type: 'string', description: 'Task globally unique identifier' },
name: { type: 'string', description: 'Task name' },
notes: { type: 'string', description: 'Task notes or description' },
completed: { type: 'boolean', description: 'Whether the task is completed' },
modified_at: { type: 'string', description: 'Task last modified timestamp' },
},
}

View File

@@ -12,6 +12,7 @@ import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
import { confluenceSearchTool } from '@/tools/confluence/search'
import { confluenceUpdateTool } from '@/tools/confluence/update'
import { confluenceUpdateCommentTool } from '@/tools/confluence/update_comment'
import { confluenceUploadAttachmentTool } from '@/tools/confluence/upload_attachment'
export {
confluenceRetrieveTool,
@@ -25,6 +26,7 @@ export {
confluenceDeleteCommentTool,
confluenceListAttachmentsTool,
confluenceDeleteAttachmentTool,
confluenceUploadAttachmentTool,
confluenceListLabelsTool,
confluenceGetSpaceTool,
confluenceListSpacesTool,

View File

@@ -142,6 +142,28 @@ export interface ConfluenceAttachmentResponse extends ToolResponse {
}
}
export interface ConfluenceUploadAttachmentParams {
accessToken: string
domain: string
pageId: string
file: any
fileName?: string
comment?: string
cloudId?: string
}
export interface ConfluenceUploadAttachmentResponse extends ToolResponse {
output: {
ts: string
attachmentId: string
title: string
fileSize: number
mediaType: string
downloadUrl: string
pageId: string
}
}
// Label operations
export interface ConfluenceLabelParams {
accessToken: string
@@ -201,5 +223,6 @@ export type ConfluenceResponse =
| ConfluenceSearchResponse
| ConfluenceCommentResponse
| ConfluenceAttachmentResponse
| ConfluenceUploadAttachmentResponse
| ConfluenceLabelResponse
| ConfluenceSpaceResponse

View File

@@ -0,0 +1,134 @@
import type { ToolConfig } from '@/tools/types'
export interface ConfluenceUploadAttachmentParams {
accessToken: string
domain: string
pageId: string
file: any
fileName?: string
comment?: string
cloudId?: string
}
export interface ConfluenceUploadAttachmentResponse {
success: boolean
output: {
ts: string
attachmentId: string
title: string
fileSize: number
mediaType: string
downloadUrl: string
pageId: string
}
}
export const confluenceUploadAttachmentTool: ToolConfig<
ConfluenceUploadAttachmentParams,
ConfluenceUploadAttachmentResponse
> = {
id: 'confluence_upload_attachment',
name: 'Confluence Upload Attachment',
description: 'Upload a file as an attachment to a Confluence page.',
version: '1.0.0',
oauth: {
required: true,
provider: 'confluence',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Confluence',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
},
pageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Confluence page ID to attach the file to',
},
file: {
type: 'file',
required: true,
visibility: 'user-or-llm',
description: 'The file to upload as an attachment',
},
fileName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional custom file name for the attachment',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional comment to add to the attachment',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
},
},
request: {
url: () => '/api/tools/confluence/upload-attachment',
method: 'POST',
headers: (params: ConfluenceUploadAttachmentParams) => {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params: ConfluenceUploadAttachmentParams) => {
return {
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
pageId: params.pageId,
file: params.file,
fileName: params.fileName,
comment: params.comment,
}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
ts: new Date().toISOString(),
attachmentId: data.attachmentId || '',
title: data.title || '',
fileSize: data.fileSize || 0,
mediaType: data.mediaType || '',
downloadUrl: data.downloadUrl || '',
pageId: data.pageId || '',
},
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of upload' },
attachmentId: { type: 'string', description: 'Uploaded attachment ID' },
title: { type: 'string', description: 'Attachment file name' },
fileSize: { type: 'number', description: 'File size in bytes' },
mediaType: { type: 'string', description: 'MIME type of the attachment' },
downloadUrl: { type: 'string', description: 'Download URL for the attachment' },
pageId: { type: 'string', description: 'Page ID the attachment was added to' },
},
}

View File

@@ -66,7 +66,11 @@ export const addMemberTool: ToolConfig<GoogleGroupsAddMemberParams, GoogleGroups
}
return {
success: true,
output: data,
output: { member: data },
}
},
outputs: {
member: { type: 'json', description: 'Added member object' },
},
}

View File

@@ -67,7 +67,11 @@ export const createGroupTool: ToolConfig<GoogleGroupsCreateParams, GoogleGroupsR
}
return {
success: true,
output: data,
output: { group: data },
}
},
outputs: {
group: { type: 'json', description: 'Created group object' },
},
}

View File

@@ -49,4 +49,8 @@ export const deleteGroupTool: ToolConfig<GoogleGroupsDeleteParams, GoogleGroupsR
output: { message: 'Group deleted successfully' },
}
},
outputs: {
message: { type: 'string', description: 'Success message' },
},
}

View File

@@ -46,7 +46,11 @@ export const getGroupTool: ToolConfig<GoogleGroupsGetParams, GoogleGroupsRespons
}
return {
success: true,
output: data,
output: { group: data },
}
},
outputs: {
group: { type: 'json', description: 'Group object' },
},
}

View File

@@ -53,7 +53,11 @@ export const getMemberTool: ToolConfig<GoogleGroupsGetMemberParams, GoogleGroups
}
return {
success: true,
output: data,
output: { member: data },
}
},
outputs: {
member: { type: 'json', description: 'Member object' },
},
}

View File

@@ -53,7 +53,11 @@ export const hasMemberTool: ToolConfig<GoogleGroupsHasMemberParams, GoogleGroups
}
return {
success: true,
output: data,
output: { isMember: data.isMember },
}
},
outputs: {
isMember: { type: 'boolean', description: 'Whether the user is a member of the group' },
},
}

View File

@@ -91,7 +91,15 @@ export const listGroupsTool: ToolConfig<GoogleGroupsListParams, GoogleGroupsResp
}
return {
success: true,
output: data,
output: {
groups: data.groups || [],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
groups: { type: 'json', description: 'Array of group objects' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -78,7 +78,15 @@ export const listMembersTool: ToolConfig<GoogleGroupsListMembersParams, GoogleGr
}
return {
success: true,
output: data,
output: {
members: data.members || [],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
members: { type: 'json', description: 'Array of member objects' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -56,4 +56,8 @@ export const removeMemberTool: ToolConfig<GoogleGroupsRemoveMemberParams, Google
output: { message: 'Member removed successfully' },
}
},
outputs: {
message: { type: 'string', description: 'Success message' },
},
}

View File

@@ -79,7 +79,11 @@ export const updateGroupTool: ToolConfig<GoogleGroupsUpdateParams, GoogleGroupsR
}
return {
success: true,
output: data,
output: { group: data },
}
},
outputs: {
group: { type: 'json', description: 'Updated group object' },
},
}

View File

@@ -64,7 +64,11 @@ export const updateMemberTool: ToolConfig<GoogleGroupsUpdateMemberParams, Google
}
return {
success: true,
output: data,
output: { member: data },
}
},
outputs: {
member: { type: 'json', description: 'Updated member object' },
},
}

View File

@@ -40,6 +40,10 @@ export const createMattersTool: ToolConfig<GoogleVaultCreateMattersParams> = {
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to create matter')
}
return { success: true, output: data }
return { success: true, output: { matter: data } }
},
outputs: {
matter: { type: 'json', description: 'Created matter object' },
},
}

View File

@@ -91,6 +91,10 @@ export const createMattersExportTool: ToolConfig<GoogleVaultCreateMattersExportP
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to create export')
}
return { success: true, output: data }
return { success: true, output: { export: data } }
},
outputs: {
export: { type: 'json', description: 'Created export object' },
},
}

View File

@@ -81,6 +81,10 @@ export const createMattersHoldsTool: ToolConfig<GoogleVaultCreateMattersHoldsPar
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to create hold')
}
return { success: true, output: data }
return { success: true, output: { hold: data } }
},
outputs: {
hold: { type: 'json', description: 'Created hold object' },
},
}

View File

@@ -31,7 +31,6 @@ export const listMattersTool: ToolConfig<GoogleVaultListMattersParams> = {
return `https://vault.googleapis.com/v1/matters/${params.matterId}`
}
const url = new URL('https://vault.googleapis.com/v1/matters')
// Handle pageSize - convert to number if needed
if (params.pageSize !== undefined && params.pageSize !== null) {
const pageSize = Number(params.pageSize)
if (Number.isFinite(pageSize) && pageSize > 0) {
@@ -39,18 +38,26 @@ export const listMattersTool: ToolConfig<GoogleVaultListMattersParams> = {
}
}
if (params.pageToken) url.searchParams.set('pageToken', params.pageToken)
// Default BASIC view implicitly by omitting 'view' and 'state' params
return url.toString()
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
},
transformResponse: async (response: Response) => {
transformResponse: async (response: Response, params?: GoogleVaultListMattersParams) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list matters')
}
if (params?.matterId) {
return { success: true, output: { matter: data } }
}
return { success: true, output: data }
},
outputs: {
matters: { type: 'json', description: 'Array of matter objects' },
matter: { type: 'json', description: 'Single matter object (when matterId is provided)' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -1,8 +1,6 @@
import type { GoogleVaultListMattersExportParams } from '@/tools/google_vault/types'
import type { ToolConfig } from '@/tools/types'
// matters.exports.list
// GET https://vault.googleapis.com/v1/matters/{matterId}/exports
export const listMattersExportTool: ToolConfig<GoogleVaultListMattersExportParams> = {
id: 'list_matters_export',
name: 'Vault List Exports (by Matter)',
@@ -28,7 +26,6 @@ export const listMattersExportTool: ToolConfig<GoogleVaultListMattersExportParam
return `https://vault.googleapis.com/v1/matters/${params.matterId}/exports/${params.exportId}`
}
const url = new URL(`https://vault.googleapis.com/v1/matters/${params.matterId}/exports`)
// Handle pageSize - convert to number if needed
if (params.pageSize !== undefined && params.pageSize !== null) {
const pageSize = Number(params.pageSize)
if (Number.isFinite(pageSize) && pageSize > 0) {
@@ -42,13 +39,20 @@ export const listMattersExportTool: ToolConfig<GoogleVaultListMattersExportParam
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
},
transformResponse: async (response: Response) => {
transformResponse: async (response: Response, params?: GoogleVaultListMattersExportParams) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list exports')
}
// Return the raw API response without modifications
if (params?.exportId) {
return { success: true, output: { export: data } }
}
return { success: true, output: data }
},
outputs: {
exports: { type: 'json', description: 'Array of export objects' },
export: { type: 'json', description: 'Single export object (when exportId is provided)' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -26,7 +26,6 @@ export const listMattersHoldsTool: ToolConfig<GoogleVaultListMattersHoldsParams>
return `https://vault.googleapis.com/v1/matters/${params.matterId}/holds/${params.holdId}`
}
const url = new URL(`https://vault.googleapis.com/v1/matters/${params.matterId}/holds`)
// Handle pageSize - convert to number if needed
if (params.pageSize !== undefined && params.pageSize !== null) {
const pageSize = Number(params.pageSize)
if (Number.isFinite(pageSize) && pageSize > 0) {
@@ -34,18 +33,26 @@ export const listMattersHoldsTool: ToolConfig<GoogleVaultListMattersHoldsParams>
}
}
if (params.pageToken) url.searchParams.set('pageToken', params.pageToken)
// Default BASIC_HOLD implicitly by omitting 'view'
return url.toString()
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
},
transformResponse: async (response: Response) => {
transformResponse: async (response: Response, params?: GoogleVaultListMattersHoldsParams) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list holds')
}
if (params?.holdId) {
return { success: true, output: { hold: data } }
}
return { success: true, output: data }
},
outputs: {
holds: { type: 'json', description: 'Array of hold objects' },
hold: { type: 'json', description: 'Single hold object (when holdId is provided)' },
nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}

View File

@@ -56,8 +56,17 @@ export const hubspotCreateCompanyTool: ToolConfig<
}
},
body: (params) => {
let properties = params.properties
if (typeof properties === 'string') {
try {
properties = JSON.parse(properties)
} catch (e) {
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
}
}
const body: any = {
properties: params.properties,
properties,
}
if (params.associations && params.associations.length > 0) {
@@ -90,21 +99,8 @@ export const hubspotCreateCompanyTool: ToolConfig<
},
outputs: {
company: { type: 'object', description: 'Created HubSpot company object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created company data',
properties: {
company: {
type: 'object',
description: 'Created company object with properties and ID',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -59,8 +59,17 @@ export const hubspotCreateContactTool: ToolConfig<
}
},
body: (params) => {
let properties = params.properties
if (typeof properties === 'string') {
try {
properties = JSON.parse(properties)
} catch (e) {
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
}
}
const body: any = {
properties: params.properties,
properties,
}
if (params.associations && params.associations.length > 0) {
@@ -93,21 +102,8 @@ export const hubspotCreateContactTool: ToolConfig<
},
outputs: {
contact: { type: 'object', description: 'Created HubSpot contact object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created contact data',
properties: {
contact: {
type: 'object',
description: 'Created contact object with properties and ID',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -103,21 +103,8 @@ export const hubspotGetCompanyTool: ToolConfig<HubSpotGetCompanyParams, HubSpotG
},
outputs: {
company: { type: 'object', description: 'HubSpot company object with properties' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Company data',
properties: {
company: {
type: 'object',
description: 'Company object with properties',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -103,21 +103,8 @@ export const hubspotGetContactTool: ToolConfig<HubSpotGetContactParams, HubSpotG
},
outputs: {
contact: { type: 'object', description: 'HubSpot contact object with properties' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Contact data',
properties: {
contact: {
type: 'object',
description: 'Contact object with properties',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -79,21 +79,8 @@ export const hubspotGetUsersTool: ToolConfig<HubSpotGetUsersParams, HubSpotGetUs
},
outputs: {
users: { type: 'array', description: 'Array of HubSpot user objects' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Users data',
properties: {
users: {
type: 'array',
description: 'Array of user objects',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -112,25 +112,9 @@ export const hubspotListCompaniesTool: ToolConfig<
},
outputs: {
companies: { type: 'array', description: 'Array of HubSpot company objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Companies data',
properties: {
companies: {
type: 'array',
description: 'Array of company objects',
},
paging: {
type: 'object',
description: 'Pagination information',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -110,25 +110,9 @@ export const hubspotListContactsTool: ToolConfig<
},
outputs: {
contacts: { type: 'array', description: 'Array of HubSpot contact objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Contacts data',
properties: {
contacts: {
type: 'array',
description: 'Array of contact objects',
},
paging: {
type: 'object',
description: 'Pagination information',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -106,25 +106,9 @@ export const hubspotListDealsTool: ToolConfig<HubSpotListDealsParams, HubSpotLis
},
outputs: {
deals: { type: 'array', description: 'Array of HubSpot deal objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deals data',
properties: {
deals: {
type: 'array',
description: 'Array of deal objects',
},
paging: {
type: 'object',
description: 'Pagination information',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -84,17 +84,47 @@ export const hubspotSearchCompaniesTool: ToolConfig<
body: (params) => {
const body: any = {}
if (params.filterGroups && params.filterGroups.length > 0) {
body.filterGroups = params.filterGroups
if (params.filterGroups) {
let parsedFilterGroups = params.filterGroups
if (typeof params.filterGroups === 'string') {
try {
parsedFilterGroups = JSON.parse(params.filterGroups)
} catch (e) {
throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedFilterGroups) && parsedFilterGroups.length > 0) {
body.filterGroups = parsedFilterGroups
}
}
if (params.sorts && params.sorts.length > 0) {
body.sorts = params.sorts
if (params.sorts) {
let parsedSorts = params.sorts
if (typeof params.sorts === 'string') {
try {
parsedSorts = JSON.parse(params.sorts)
} catch (e) {
throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
body.sorts = parsedSorts
}
}
if (params.query) {
body.query = params.query
}
if (params.properties && params.properties.length > 0) {
body.properties = params.properties
if (params.properties) {
let parsedProperties = params.properties
if (typeof params.properties === 'string') {
try {
parsedProperties = JSON.parse(params.properties)
} catch (e) {
throw new Error(`Invalid JSON for properties: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedProperties) && parsedProperties.length > 0) {
body.properties = parsedProperties
}
}
if (params.limit) {
body.limit = params.limit
@@ -115,46 +145,29 @@ export const hubspotSearchCompaniesTool: ToolConfig<
throw new Error(data.message || 'Failed to search companies in HubSpot')
}
const result = {
companies: data.results || [],
total: data.total,
paging: data.paging,
metadata: {
operation: 'search_companies' as const,
totalReturned: data.results?.length || 0,
total: data.total,
},
}
return {
success: true,
output: {
companies: data.results || [],
total: data.total,
paging: data.paging,
metadata: {
operation: 'search_companies' as const,
totalReturned: data.results?.length || 0,
total: data.total,
},
success: true,
},
output: result,
...result,
}
},
outputs: {
companies: { type: 'array', description: 'Array of matching HubSpot company objects' },
total: { type: 'number', description: 'Total number of matching companies' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Search results',
properties: {
companies: {
type: 'array',
description: 'Array of matching company objects',
},
total: {
type: 'number',
description: 'Total number of matching companies',
},
paging: {
type: 'object',
description: 'Pagination information',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -84,17 +84,47 @@ export const hubspotSearchContactsTool: ToolConfig<
body: (params) => {
const body: any = {}
if (params.filterGroups && params.filterGroups.length > 0) {
body.filterGroups = params.filterGroups
if (params.filterGroups) {
let parsedFilterGroups = params.filterGroups
if (typeof params.filterGroups === 'string') {
try {
parsedFilterGroups = JSON.parse(params.filterGroups)
} catch (e) {
throw new Error(`Invalid JSON for filterGroups: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedFilterGroups) && parsedFilterGroups.length > 0) {
body.filterGroups = parsedFilterGroups
}
}
if (params.sorts && params.sorts.length > 0) {
body.sorts = params.sorts
if (params.sorts) {
let parsedSorts = params.sorts
if (typeof params.sorts === 'string') {
try {
parsedSorts = JSON.parse(params.sorts)
} catch (e) {
throw new Error(`Invalid JSON for sorts: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {
body.sorts = parsedSorts
}
}
if (params.query) {
body.query = params.query
}
if (params.properties && params.properties.length > 0) {
body.properties = params.properties
if (params.properties) {
let parsedProperties = params.properties
if (typeof params.properties === 'string') {
try {
parsedProperties = JSON.parse(params.properties)
} catch (e) {
throw new Error(`Invalid JSON for properties: ${(e as Error).message}`)
}
}
if (Array.isArray(parsedProperties) && parsedProperties.length > 0) {
body.properties = parsedProperties
}
}
if (params.limit) {
body.limit = params.limit
@@ -115,46 +145,29 @@ export const hubspotSearchContactsTool: ToolConfig<
throw new Error(data.message || 'Failed to search contacts in HubSpot')
}
const result = {
contacts: data.results || [],
total: data.total,
paging: data.paging,
metadata: {
operation: 'search_contacts' as const,
totalReturned: data.results?.length || 0,
total: data.total,
},
}
return {
success: true,
output: {
contacts: data.results || [],
total: data.total,
paging: data.paging,
metadata: {
operation: 'search_contacts' as const,
totalReturned: data.results?.length || 0,
total: data.total,
},
success: true,
},
output: result,
...result,
}
},
outputs: {
contacts: { type: 'array', description: 'Array of matching HubSpot contact objects' },
total: { type: 'number', description: 'Total number of matching contacts' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Search results',
properties: {
contacts: {
type: 'array',
description: 'Array of matching contact objects',
},
total: {
type: 'number',
description: 'Total number of matching contacts',
},
paging: {
type: 'object',
description: 'Pagination information',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -131,16 +131,13 @@ export interface HubSpotUpdateContactParams {
// Search Contacts
export interface HubSpotSearchContactsResponse extends ToolResponse {
output: {
contacts: HubSpotContact[]
contacts: HubSpotContact[]
total: number
paging?: HubSpotPaging
metadata: {
operation: 'search_contacts'
totalReturned: number
total: number
paging?: HubSpotPaging
metadata: {
operation: 'search_contacts'
totalReturned: number
total: number
}
success: boolean
}
}
@@ -212,17 +209,14 @@ export type HubSpotUpdateCompanyResponse = Omit<HubSpotUpdateContactResponse, 'o
}
}
export type HubSpotSearchCompaniesParams = HubSpotSearchContactsParams
export type HubSpotSearchCompaniesResponse = Omit<HubSpotSearchContactsResponse, 'output'> & {
output: {
companies: HubSpotContact[]
export interface HubSpotSearchCompaniesResponse extends ToolResponse {
companies: HubSpotContact[]
total: number
paging?: HubSpotPaging
metadata: {
operation: 'search_companies'
totalReturned: number
total: number
paging?: HubSpotPaging
metadata: {
operation: 'search_companies'
totalReturned: number
total: number
}
success: boolean
}
}

View File

@@ -69,8 +69,17 @@ export const hubspotUpdateCompanyTool: ToolConfig<
}
},
body: (params) => {
let properties = params.properties
if (typeof properties === 'string') {
try {
properties = JSON.parse(properties)
} catch (e) {
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
}
}
return {
properties: params.properties,
properties,
}
},
},
@@ -97,21 +106,8 @@ export const hubspotUpdateCompanyTool: ToolConfig<
},
outputs: {
company: { type: 'object', description: 'Updated HubSpot company object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated company data',
properties: {
company: {
type: 'object',
description: 'Updated company object with properties',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -69,8 +69,17 @@ export const hubspotUpdateContactTool: ToolConfig<
}
},
body: (params) => {
let properties = params.properties
if (typeof properties === 'string') {
try {
properties = JSON.parse(properties)
} catch (e) {
throw new Error('Invalid JSON format for properties. Please provide a valid JSON object.')
}
}
return {
properties: params.properties,
properties,
}
},
},
@@ -97,21 +106,8 @@ export const hubspotUpdateContactTool: ToolConfig<
},
outputs: {
contact: { type: 'object', description: 'Updated HubSpot contact object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated contact data',
properties: {
contact: {
type: 'object',
description: 'Updated contact object with properties',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -63,32 +63,25 @@ export const uploadTool: ToolConfig<OneDriveToolParams, OneDriveUploadResponse>
request: {
url: (params) => {
// If file is provided OR Excel file is being created, use custom API route
const isExcelFile =
params.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
if (params.file || isExcelFile) {
return '/api/tools/onedrive/upload'
}
// Direct upload for text files - use Microsoft Graph API
let fileName = params.fileName || 'untitled'
// For text files, ensure .txt extension
if (!fileName.endsWith('.txt')) {
// Remove any existing extensions and add .txt
fileName = `${fileName.replace(/\.[^.]*$/, '')}.txt`
}
// Build the proper URL based on parent folder
const parentFolderId = params.manualFolderId || params.folderSelector
if (parentFolderId && parentFolderId.trim() !== '') {
return `https://graph.microsoft.com/v1.0/me/drive/items/${encodeURIComponent(parentFolderId)}:/${fileName}:/content`
}
// Default to root folder
return `https://graph.microsoft.com/v1.0/me/drive/root:/${fileName}:/content`
},
method: (params) => {
// Use POST for custom API route (file uploads or Excel creation), PUT for direct text upload
const isExcelFile =
params.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
return params.file || isExcelFile ? 'POST' : 'PUT'
@@ -98,11 +91,9 @@ export const uploadTool: ToolConfig<OneDriveToolParams, OneDriveUploadResponse>
const isExcelFile =
params.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
// For file uploads or Excel creation via custom API, send JSON
if (params.file || isExcelFile) {
headers['Content-Type'] = 'application/json'
} else {
// For direct text uploads, use direct PUT with access token
headers.Authorization = `Bearer ${params.accessToken}`
headers['Content-Type'] = 'text/plain'
}
@@ -112,20 +103,17 @@ export const uploadTool: ToolConfig<OneDriveToolParams, OneDriveUploadResponse>
const isExcelFile =
params.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
// For file uploads or Excel creation, send all params as JSON to custom API route
if (params.file || isExcelFile) {
return {
accessToken: params.accessToken,
fileName: params.fileName,
file: params.file,
folderId: params.manualFolderId || params.folderSelector,
mimeType: params.mimeType,
// Optional Excel content write-after-create
values: params.values,
...(params.mimeType && { mimeType: params.mimeType }),
...(params.values && { values: params.values }),
}
}
// For text files, send content directly
return (params.content || '') as unknown as Record<string, unknown>
},
},
@@ -136,7 +124,6 @@ export const uploadTool: ToolConfig<OneDriveToolParams, OneDriveUploadResponse>
const isExcelFile =
params?.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
// Handle response from custom API route (for file uploads or Excel creation)
if ((params?.file || isExcelFile) && data.success !== undefined) {
if (!data.success) {
throw new Error(data.error || 'Failed to upload file')
@@ -153,7 +140,6 @@ export const uploadTool: ToolConfig<OneDriveToolParams, OneDriveUploadResponse>
}
}
// Handle response from direct Microsoft Graph API (for text-only uploads)
const fileData = data
logger.info('Successfully uploaded file to OneDrive', {

View File

@@ -132,21 +132,8 @@ export const pipedriveCreateActivityTool: ToolConfig<
},
outputs: {
activity: { type: 'object', description: 'The created activity object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created activity details',
properties: {
activity: {
type: 'object',
description: 'The created activity object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -132,21 +132,8 @@ export const pipedriveCreateDealTool: ToolConfig<
},
outputs: {
deal: { type: 'object', description: 'The created deal object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created deal details',
properties: {
deal: {
type: 'object',
description: 'The created deal object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -141,21 +141,8 @@ export const pipedriveCreateLeadTool: ToolConfig<
},
outputs: {
lead: { type: 'object', description: 'The created lead object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created lead details',
properties: {
lead: {
type: 'object',
description: 'The created lead object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -97,21 +97,8 @@ export const pipedriveCreateProjectTool: ToolConfig<
},
outputs: {
project: { type: 'object', description: 'The created project object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created project details',
properties: {
project: {
type: 'object',
description: 'The created project object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -72,21 +72,8 @@ export const pipedriveDeleteLeadTool: ToolConfig<
},
outputs: {
data: { type: 'object', description: 'Deletion confirmation data' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deletion result',
properties: {
data: {
type: 'object',
description: 'Deletion confirmation data',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -113,21 +113,8 @@ export const pipedriveGetActivitiesTool: ToolConfig<
},
outputs: {
activities: { type: 'array', description: 'Array of activity objects from Pipedrive' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Activities data',
properties: {
activities: {
type: 'array',
description: 'Array of activity objects from Pipedrive',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -118,44 +118,8 @@ export const pipedriveGetAllDealsTool: ToolConfig<
},
outputs: {
deals: { type: 'array', description: 'Array of deal objects from Pipedrive' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deals data and metadata',
properties: {
deals: {
type: 'array',
description: 'Array of deal objects from Pipedrive',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Deal ID' },
title: { type: 'string', description: 'Deal title' },
value: { type: 'number', description: 'Deal value' },
currency: { type: 'string', description: 'Deal currency' },
status: { type: 'string', description: 'Deal status' },
stage_id: { type: 'number', description: 'Stage ID' },
pipeline_id: { type: 'number', description: 'Pipeline ID' },
owner_id: { type: 'number', description: 'Owner user ID' },
add_time: { type: 'string', description: 'Deal creation time' },
update_time: { type: 'string', description: 'Deal last update time' },
},
},
},
metadata: {
type: 'object',
description: 'Operation metadata',
properties: {
operation: { type: 'string', description: 'The operation performed' },
totalItems: { type: 'number', description: 'Total number of deals returned' },
hasMore: {
type: 'boolean',
description: 'Whether there are more items to fetch via pagination',
},
},
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -61,21 +61,8 @@ export const pipedriveGetDealTool: ToolConfig<PipedriveGetDealParams, PipedriveG
},
outputs: {
deal: { type: 'object', description: 'Deal object with full details' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deal details',
properties: {
deal: {
type: 'object',
description: 'Deal object with full details',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -94,21 +94,8 @@ export const pipedriveGetFilesTool: ToolConfig<PipedriveGetFilesParams, Pipedriv
},
outputs: {
files: { type: 'array', description: 'Array of file objects from Pipedrive' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Files data',
properties: {
files: {
type: 'array',
description: 'Array of file objects from Pipedrive',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -136,25 +136,9 @@ export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, Pipedriv
},
outputs: {
leads: { type: 'array', description: 'Array of lead objects (when listing all)' },
lead: { type: 'object', description: 'Single lead object (when lead_id is provided)' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Leads data or single lead details',
properties: {
leads: {
type: 'array',
description: 'Array of lead objects (when listing all)',
},
lead: {
type: 'object',
description: 'Single lead object (when lead_id is provided)',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -90,21 +90,8 @@ export const pipedriveGetMailMessagesTool: ToolConfig<
},
outputs: {
messages: { type: 'array', description: 'Array of mail thread objects from Pipedrive mailbox' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Mail threads data',
properties: {
messages: {
type: 'array',
description: 'Array of mail thread objects from Pipedrive mailbox',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -77,21 +77,8 @@ export const pipedriveGetMailThreadTool: ToolConfig<
},
outputs: {
messages: { type: 'array', description: 'Array of mail message objects from the thread' },
metadata: { type: 'object', description: 'Operation metadata including thread ID' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Mail thread messages data',
properties: {
messages: {
type: 'array',
description: 'Array of mail message objects from the thread',
},
metadata: {
type: 'object',
description: 'Operation metadata including thread ID',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -99,21 +99,8 @@ export const pipedriveGetPipelineDealsTool: ToolConfig<
},
outputs: {
deals: { type: 'array', description: 'Array of deal objects from the pipeline' },
metadata: { type: 'object', description: 'Operation metadata including pipeline ID' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Pipeline deals data',
properties: {
deals: {
type: 'array',
description: 'Array of deal objects from the pipeline',
},
metadata: {
type: 'object',
description: 'Operation metadata including pipeline ID',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -99,21 +99,8 @@ export const pipedriveGetPipelinesTool: ToolConfig<
},
outputs: {
pipelines: { type: 'array', description: 'Array of pipeline objects from Pipedrive' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Pipelines data',
properties: {
pipelines: {
type: 'array',
description: 'Array of pipeline objects from Pipedrive',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -112,25 +112,9 @@ export const pipedriveGetProjectsTool: ToolConfig<
},
outputs: {
projects: { type: 'array', description: 'Array of project objects (when listing all)' },
project: { type: 'object', description: 'Single project object (when project_id is provided)' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Projects data or single project details',
properties: {
projects: {
type: 'array',
description: 'Array of project objects (when listing all)',
},
project: {
type: 'object',
description: 'Single project object (when project_id is provided)',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -116,21 +116,8 @@ export const pipedriveUpdateActivityTool: ToolConfig<
},
outputs: {
activity: { type: 'object', description: 'The updated activity object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated activity details',
properties: {
activity: {
type: 'object',
description: 'The updated activity object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -109,21 +109,8 @@ export const pipedriveUpdateDealTool: ToolConfig<
},
outputs: {
deal: { type: 'object', description: 'The updated deal object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated deal details',
properties: {
deal: {
type: 'object',
description: 'The updated deal object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -142,21 +142,8 @@ export const pipedriveUpdateLeadTool: ToolConfig<
},
outputs: {
lead: { type: 'object', description: 'The updated lead object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated lead details',
properties: {
lead: {
type: 'object',
description: 'The updated lead object',
},
metadata: {
type: 'object',
description: 'Operation metadata',
},
success: { type: 'boolean', description: 'Operation success status' },
},
},
},
}

View File

@@ -80,6 +80,7 @@ import {
confluenceSearchTool,
confluenceUpdateCommentTool,
confluenceUpdateTool,
confluenceUploadAttachmentTool,
} from '@/tools/confluence'
import {
cursorAddFollowupTool,
@@ -1807,6 +1808,7 @@ export const tools: Record<string, ToolConfig> = {
confluence_update_comment: confluenceUpdateCommentTool,
confluence_delete_comment: confluenceDeleteCommentTool,
confluence_list_attachments: confluenceListAttachmentsTool,
confluence_upload_attachment: confluenceUploadAttachmentTool,
confluence_delete_attachment: confluenceDeleteAttachmentTool,
confluence_list_labels: confluenceListLabelsTool,
confluence_get_space: confluenceGetSpaceTool,