mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
more integrations
This commit is contained in:
@@ -7,6 +7,7 @@ import binaryExtensionsList from 'binary-extensions'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||||
import { secureFetchWithPinnedIP, validateUrlWithDNS } from '@/lib/core/security/input-validation'
|
import { secureFetchWithPinnedIP, validateUrlWithDNS } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
|
import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
|
||||||
import { isUsingCloudStorage, type StorageContext, StorageService } from '@/lib/uploads'
|
import { isUsingCloudStorage, type StorageContext, StorageService } from '@/lib/uploads'
|
||||||
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
|
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
|
||||||
@@ -367,7 +368,7 @@ async function handleExternalUrl(
|
|||||||
throw new Error(`File too large: ${buffer.length} bytes (max: ${MAX_DOWNLOAD_SIZE_BYTES})`)
|
throw new Error(`File too large: ${buffer.length} bytes (max: ${MAX_DOWNLOAD_SIZE_BYTES})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Downloaded file from URL: ${url}, size: ${buffer.length} bytes`)
|
logger.info(`Downloaded file from URL: ${sanitizeUrlForLog(url)}, size: ${buffer.length} bytes`)
|
||||||
|
|
||||||
let userFile: UserFile | undefined
|
let userFile: UserFile | undefined
|
||||||
const mimeType = response.headers.get('content-type') || getMimeTypeFromExtension(extension)
|
const mimeType = response.headers.get('content-type') || getMimeTypeFromExtension(extension)
|
||||||
@@ -420,7 +421,7 @@ async function handleExternalUrl(
|
|||||||
|
|
||||||
return parseResult
|
return parseResult
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error handling external URL ${url}:`, error)
|
logger.error(`Error handling external URL ${sanitizeUrlForLog(url)}:`, error)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Error fetching URL: ${(error as Error).message}`,
|
error: `Error fetching URL: ${(error as Error).message}`,
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ export async function POST(request: NextRequest) {
|
|||||||
logger.info(`[${requestId}] Processing ${validatedData.files.length} file(s)`)
|
logger.info(`[${requestId}] Processing ${validatedData.files.length} file(s)`)
|
||||||
|
|
||||||
const userFiles = processFilesToUserFiles(validatedData.files, requestId, logger)
|
const userFiles = processFilesToUserFiles(validatedData.files, requestId, logger)
|
||||||
|
const filesOutput: Array<{
|
||||||
|
name: string
|
||||||
|
mimeType: string
|
||||||
|
data: string
|
||||||
|
size: number
|
||||||
|
}> = []
|
||||||
|
|
||||||
if (userFiles.length === 0) {
|
if (userFiles.length === 0) {
|
||||||
logger.warn(`[${requestId}] No valid files to upload, falling back to text-only`)
|
logger.warn(`[${requestId}] No valid files to upload, falling back to text-only`)
|
||||||
@@ -138,6 +144,12 @@ export async function POST(request: NextRequest) {
|
|||||||
logger.info(`[${requestId}] Downloading file ${i}: ${userFile.name}`)
|
logger.info(`[${requestId}] Downloading file ${i}: ${userFile.name}`)
|
||||||
|
|
||||||
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
||||||
|
filesOutput.push({
|
||||||
|
name: userFile.name,
|
||||||
|
mimeType: userFile.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
})
|
||||||
|
|
||||||
const blob = new Blob([new Uint8Array(buffer)], { type: userFile.type })
|
const blob = new Blob([new Uint8Array(buffer)], { type: userFile.type })
|
||||||
formData.append(`files[${i}]`, blob, userFile.name)
|
formData.append(`files[${i}]`, blob, userFile.name)
|
||||||
@@ -174,6 +186,7 @@ export async function POST(request: NextRequest) {
|
|||||||
message: data.content,
|
message: data.content,
|
||||||
data: data,
|
data: data,
|
||||||
fileCount: userFiles.length,
|
fileCount: userFiles.length,
|
||||||
|
files: filesOutput,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateImageUrl } from '@/lib/core/security/input-validation'
|
import { validateImageUrl } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
|
||||||
const logger = createLogger('ImageProxyAPI')
|
const logger = createLogger('ImageProxyAPI')
|
||||||
@@ -29,13 +30,13 @@ export async function GET(request: NextRequest) {
|
|||||||
const urlValidation = validateImageUrl(imageUrl)
|
const urlValidation = validateImageUrl(imageUrl)
|
||||||
if (!urlValidation.isValid) {
|
if (!urlValidation.isValid) {
|
||||||
logger.warn(`[${requestId}] Blocked image proxy request`, {
|
logger.warn(`[${requestId}] Blocked image proxy request`, {
|
||||||
url: imageUrl.substring(0, 100),
|
url: sanitizeUrlForLog(imageUrl),
|
||||||
error: urlValidation.error,
|
error: urlValidation.error,
|
||||||
})
|
})
|
||||||
return new NextResponse(urlValidation.error || 'Invalid image URL', { status: 403 })
|
return new NextResponse(urlValidation.error || 'Invalid image URL', { status: 403 })
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`[${requestId}] Proxying image request for: ${imageUrl}`)
|
logger.info(`[${requestId}] Proxying image request for: ${sanitizeUrlForLog(imageUrl)}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const imageResponse = await fetch(imageUrl, {
|
const imageResponse = await fetch(imageUrl, {
|
||||||
|
|||||||
121
apps/sim/app/api/tools/jira/add-attachment/route.ts
Normal file
121
apps/sim/app/api/tools/jira/add-attachment/route.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||||
|
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||||
|
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||||
|
import { getJiraCloudId } from '@/tools/jira/utils'
|
||||||
|
|
||||||
|
const logger = createLogger('JiraAddAttachmentAPI')
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
const JiraAddAttachmentSchema = z.object({
|
||||||
|
accessToken: z.string().min(1, 'Access token is required'),
|
||||||
|
domain: z.string().min(1, 'Domain is required'),
|
||||||
|
issueKey: z.string().min(1, 'Issue key is required'),
|
||||||
|
files: RawFileInputArraySchema,
|
||||||
|
cloudId: z.string().optional().nullable(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const requestId = `jira-attach-${Date.now()}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||||
|
if (!authResult.success) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: authResult.error || 'Unauthorized' },
|
||||||
|
{ status: 401 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await request.json()
|
||||||
|
const validatedData = JiraAddAttachmentSchema.parse(body)
|
||||||
|
|
||||||
|
const userFiles = processFilesToUserFiles(validatedData.files, requestId, logger)
|
||||||
|
if (userFiles.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'No valid files provided for upload' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloudId =
|
||||||
|
validatedData.cloudId ||
|
||||||
|
(await getJiraCloudId(validatedData.domain, validatedData.accessToken))
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
const filesOutput: Array<{ name: string; mimeType: string; data: string; size: number }> = []
|
||||||
|
|
||||||
|
for (const file of userFiles) {
|
||||||
|
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
||||||
|
filesOutput.push({
|
||||||
|
name: file.name,
|
||||||
|
mimeType: file.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
})
|
||||||
|
const blob = new Blob([new Uint8Array(buffer)], {
|
||||||
|
type: file.type || 'application/octet-stream',
|
||||||
|
})
|
||||||
|
formData.append('file', blob, file.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/issue/${validatedData.issueKey}/attachments`
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${validatedData.accessToken}`,
|
||||||
|
'X-Atlassian-Token': 'no-check',
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text()
|
||||||
|
logger.error(`[${requestId}] Jira attachment upload failed`, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
error: errorText,
|
||||||
|
})
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: `Failed to upload attachments: ${response.statusText}`,
|
||||||
|
},
|
||||||
|
{ status: response.status }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = await response.json()
|
||||||
|
const attachmentIds = Array.isArray(attachments)
|
||||||
|
? attachments.map((attachment) => attachment.id).filter(Boolean)
|
||||||
|
: []
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
output: {
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
issueKey: validatedData.issueKey,
|
||||||
|
attachmentIds,
|
||||||
|
files: filesOutput,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: 'Invalid request data', details: error.errors },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error(`[${requestId}] Jira attachment upload error`, error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: error instanceof Error ? error.message : 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -62,7 +63,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/queue${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `${baseUrl}/servicedesk/${serviceDeskId}/queue${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
logger.info('Fetching queues from:', url)
|
logger.info('Fetching queues from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
validateJiraCloudId,
|
validateJiraCloudId,
|
||||||
validateJiraIssueKey,
|
validateJiraIssueKey,
|
||||||
} from '@/lib/core/security/input-validation'
|
} from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -66,7 +67,7 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
const url = `${baseUrl}/request`
|
const url = `${baseUrl}/request`
|
||||||
|
|
||||||
logger.info('Creating request at:', url)
|
logger.info('Creating request at:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const requestBody: Record<string, unknown> = {
|
const requestBody: Record<string, unknown> = {
|
||||||
serviceDeskId,
|
serviceDeskId,
|
||||||
@@ -128,7 +129,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}`
|
const url = `${baseUrl}/request/${issueIdOrKey}`
|
||||||
|
|
||||||
logger.info('Fetching request from:', url)
|
logger.info('Fetching request from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -68,7 +69,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/request${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `${baseUrl}/request${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
logger.info('Fetching requests from:', url)
|
logger.info('Fetching requests from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -53,7 +54,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/requesttype${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `${baseUrl}/servicedesk/${serviceDeskId}/requesttype${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
logger.info('Fetching request types from:', url)
|
logger.info('Fetching request types from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
|
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -43,7 +44,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/servicedesk${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `${baseUrl}/servicedesk${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
logger.info('Fetching service desks from:', url)
|
logger.info('Fetching service desks from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -53,7 +54,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/sla${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `${baseUrl}/request/${issueIdOrKey}/sla${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
logger.info('Fetching SLA info from:', url)
|
logger.info('Fetching SLA info from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
validateJiraCloudId,
|
validateJiraCloudId,
|
||||||
validateJiraIssueKey,
|
validateJiraIssueKey,
|
||||||
} from '@/lib/core/security/input-validation'
|
} from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -69,7 +70,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
|
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
|
||||||
|
|
||||||
logger.info('Transitioning request at:', url)
|
logger.info('Transitioning request at:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const body: Record<string, unknown> = {
|
const body: Record<string, unknown> = {
|
||||||
id: transitionId,
|
id: transitionId,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -49,7 +50,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
|
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
|
||||||
|
|
||||||
logger.info('Fetching transitions from:', url)
|
logger.info('Fetching transitions from:', sanitizeUrlForLog(url))
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||||
@@ -55,6 +56,12 @@ export async function POST(request: NextRequest) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const attachments: any[] = []
|
const attachments: any[] = []
|
||||||
|
const filesOutput: Array<{
|
||||||
|
name: string
|
||||||
|
mimeType: string
|
||||||
|
data: string
|
||||||
|
size: number
|
||||||
|
}> = []
|
||||||
if (validatedData.files && validatedData.files.length > 0) {
|
if (validatedData.files && validatedData.files.length > 0) {
|
||||||
const rawFiles = validatedData.files
|
const rawFiles = validatedData.files
|
||||||
logger.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to OneDrive`)
|
logger.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to OneDrive`)
|
||||||
@@ -66,13 +73,19 @@ export async function POST(request: NextRequest) {
|
|||||||
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
|
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
|
||||||
|
|
||||||
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
||||||
|
filesOutput.push({
|
||||||
|
name: file.name,
|
||||||
|
mimeType: file.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
})
|
||||||
|
|
||||||
const uploadUrl =
|
const uploadUrl =
|
||||||
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
|
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
|
||||||
encodeURIComponent(file.name) +
|
encodeURIComponent(file.name) +
|
||||||
':/content'
|
':/content'
|
||||||
|
|
||||||
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
|
logger.info(`[${requestId}] Uploading to Teams: ${sanitizeUrlForLog(uploadUrl)}`)
|
||||||
|
|
||||||
const uploadResponse = await fetch(uploadUrl, {
|
const uploadResponse = await fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -238,6 +251,7 @@ export async function POST(request: NextRequest) {
|
|||||||
url: responseData.webUrl || '',
|
url: responseData.webUrl || '',
|
||||||
attachmentCount: attachments.length,
|
attachmentCount: attachments.length,
|
||||||
},
|
},
|
||||||
|
files: filesOutput,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||||
@@ -53,6 +54,12 @@ export async function POST(request: NextRequest) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const attachments: any[] = []
|
const attachments: any[] = []
|
||||||
|
const filesOutput: Array<{
|
||||||
|
name: string
|
||||||
|
mimeType: string
|
||||||
|
data: string
|
||||||
|
size: number
|
||||||
|
}> = []
|
||||||
if (validatedData.files && validatedData.files.length > 0) {
|
if (validatedData.files && validatedData.files.length > 0) {
|
||||||
const rawFiles = validatedData.files
|
const rawFiles = validatedData.files
|
||||||
logger.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to Teams`)
|
logger.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to Teams`)
|
||||||
@@ -64,13 +71,19 @@ export async function POST(request: NextRequest) {
|
|||||||
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
|
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
|
||||||
|
|
||||||
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
const buffer = await downloadFileFromStorage(file, requestId, logger)
|
||||||
|
filesOutput.push({
|
||||||
|
name: file.name,
|
||||||
|
mimeType: file.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
})
|
||||||
|
|
||||||
const uploadUrl =
|
const uploadUrl =
|
||||||
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
|
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
|
||||||
encodeURIComponent(file.name) +
|
encodeURIComponent(file.name) +
|
||||||
':/content'
|
':/content'
|
||||||
|
|
||||||
logger.info(`[${requestId}] Uploading to Teams: ${uploadUrl}`)
|
logger.info(`[${requestId}] Uploading to Teams: ${sanitizeUrlForLog(uploadUrl)}`)
|
||||||
|
|
||||||
const uploadResponse = await fetch(uploadUrl, {
|
const uploadResponse = await fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -234,6 +247,7 @@ export async function POST(request: NextRequest) {
|
|||||||
url: responseData.webUrl || '',
|
url: responseData.webUrl || '',
|
||||||
attachmentCount: attachments.length,
|
attachmentCount: attachments.length,
|
||||||
},
|
},
|
||||||
|
files: filesOutput,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||||
import { StorageService } from '@/lib/uploads'
|
import { StorageService } from '@/lib/uploads'
|
||||||
|
import { FileInputSchema } from '@/lib/uploads/utils/file-schemas'
|
||||||
import {
|
import {
|
||||||
extractStorageKey,
|
extractStorageKey,
|
||||||
inferContextFromKey,
|
inferContextFromKey,
|
||||||
@@ -19,7 +20,7 @@ const logger = createLogger('MistralParseAPI')
|
|||||||
const MistralParseSchema = z.object({
|
const MistralParseSchema = z.object({
|
||||||
apiKey: z.string().min(1, 'API key is required'),
|
apiKey: z.string().min(1, 'API key is required'),
|
||||||
filePath: z.string().min(1, 'File path is required').optional(),
|
filePath: z.string().min(1, 'File path is required').optional(),
|
||||||
fileData: z.unknown().optional(),
|
fileData: FileInputSchema.optional(),
|
||||||
resultType: z.string().optional(),
|
resultType: z.string().optional(),
|
||||||
pages: z.array(z.number()).optional(),
|
pages: z.array(z.number()).optional(),
|
||||||
includeImageBase64: z.boolean().optional(),
|
includeImageBase64: z.boolean().optional(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
|
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
|
||||||
import { createSftpConnection, getSftp, isPathSafe, sanitizePath } from '@/app/api/tools/sftp/utils'
|
import { createSftpConnection, getSftp, isPathSafe, sanitizePath } from '@/app/api/tools/sftp/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -111,6 +112,8 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const buffer = Buffer.concat(chunks)
|
const buffer = Buffer.concat(chunks)
|
||||||
const fileName = path.basename(remotePath)
|
const fileName = path.basename(remotePath)
|
||||||
|
const extension = getFileExtension(fileName)
|
||||||
|
const mimeType = getMimeTypeFromExtension(extension)
|
||||||
|
|
||||||
let content: string
|
let content: string
|
||||||
if (params.encoding === 'base64') {
|
if (params.encoding === 'base64') {
|
||||||
@@ -124,6 +127,12 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
fileName,
|
fileName,
|
||||||
|
file: {
|
||||||
|
name: fileName,
|
||||||
|
mimeType,
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
},
|
||||||
content,
|
content,
|
||||||
size: buffer.length,
|
size: buffer.length,
|
||||||
encoding: params.encoding,
|
encoding: params.encoding,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { generateRequestId } from '@/lib/core/utils/request'
|
import { generateRequestId } from '@/lib/core/utils/request'
|
||||||
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
|
||||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||||
@@ -144,7 +145,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const uploadUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drives/${effectiveDriveId}/root:${encodedPath}:/content`
|
const uploadUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drives/${effectiveDriveId}/root:${encodedPath}:/content`
|
||||||
|
|
||||||
logger.info(`[${requestId}] Uploading to: ${uploadUrl}`)
|
logger.info(`[${requestId}] Uploading to: ${sanitizeUrlForLog(uploadUrl)}`)
|
||||||
|
|
||||||
const uploadResponse = await fetch(uploadUrl, {
|
const uploadResponse = await fetch(uploadUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Logger } from '@sim/logger'
|
import type { Logger } from '@sim/logger'
|
||||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||||
|
import type { ToolFileData } from '@/tools/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to a Slack channel using chat.postMessage
|
* Sends a message to a Slack channel using chat.postMessage
|
||||||
@@ -70,14 +71,21 @@ export async function uploadFilesToSlack(
|
|||||||
accessToken: string,
|
accessToken: string,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
logger: Logger
|
logger: Logger
|
||||||
): Promise<string[]> {
|
): Promise<{ fileIds: string[]; files: ToolFileData[] }> {
|
||||||
const userFiles = processFilesToUserFiles(files, requestId, logger)
|
const userFiles = processFilesToUserFiles(files, requestId, logger)
|
||||||
const uploadedFileIds: string[] = []
|
const uploadedFileIds: string[] = []
|
||||||
|
const uploadedFiles: ToolFileData[] = []
|
||||||
|
|
||||||
for (const userFile of userFiles) {
|
for (const userFile of userFiles) {
|
||||||
logger.info(`[${requestId}] Uploading file: ${userFile.name}`)
|
logger.info(`[${requestId}] Uploading file: ${userFile.name}`)
|
||||||
|
|
||||||
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
||||||
|
uploadedFiles.push({
|
||||||
|
name: userFile.name,
|
||||||
|
mimeType: userFile.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
})
|
||||||
|
|
||||||
const getUrlResponse = await fetch('https://slack.com/api/files.getUploadURLExternal', {
|
const getUrlResponse = await fetch('https://slack.com/api/files.getUploadURLExternal', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -114,7 +122,7 @@ export async function uploadFilesToSlack(
|
|||||||
uploadedFileIds.push(urlData.file_id)
|
uploadedFileIds.push(urlData.file_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadedFileIds
|
return { fileIds: uploadedFileIds, files: uploadedFiles }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,7 +225,13 @@ export async function sendSlackMessage(
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: boolean
|
success: boolean
|
||||||
output?: { message: any; ts: string; channel: string; fileCount?: number }
|
output?: {
|
||||||
|
message: any
|
||||||
|
ts: string
|
||||||
|
channel: string
|
||||||
|
fileCount?: number
|
||||||
|
files?: ToolFileData[]
|
||||||
|
}
|
||||||
error?: string
|
error?: string
|
||||||
}> {
|
}> {
|
||||||
const { accessToken, text, threadTs, files } = params
|
const { accessToken, text, threadTs, files } = params
|
||||||
@@ -249,10 +263,15 @@ export async function sendSlackMessage(
|
|||||||
|
|
||||||
// Process files
|
// Process files
|
||||||
logger.info(`[${requestId}] Processing ${files.length} file(s)`)
|
logger.info(`[${requestId}] Processing ${files.length} file(s)`)
|
||||||
const uploadedFileIds = await uploadFilesToSlack(files, accessToken, requestId, logger)
|
const { fileIds, files: uploadedFiles } = await uploadFilesToSlack(
|
||||||
|
files,
|
||||||
|
accessToken,
|
||||||
|
requestId,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
|
||||||
// No valid files uploaded - send text-only
|
// No valid files uploaded - send text-only
|
||||||
if (uploadedFileIds.length === 0) {
|
if (fileIds.length === 0) {
|
||||||
logger.warn(`[${requestId}] No valid files to upload, sending text-only message`)
|
logger.warn(`[${requestId}] No valid files to upload, sending text-only message`)
|
||||||
|
|
||||||
const data = await postSlackMessage(accessToken, channel, text, threadTs)
|
const data = await postSlackMessage(accessToken, channel, text, threadTs)
|
||||||
@@ -265,7 +284,7 @@ export async function sendSlackMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Complete file upload
|
// Complete file upload
|
||||||
const completeData = await completeSlackFileUpload(uploadedFileIds, channel, text, accessToken)
|
const completeData = await completeSlackFileUpload(fileIds, channel, text, accessToken)
|
||||||
|
|
||||||
if (!completeData.ok) {
|
if (!completeData.ok) {
|
||||||
logger.error(`[${requestId}] Failed to complete upload:`, completeData.error)
|
logger.error(`[${requestId}] Failed to complete upload:`, completeData.error)
|
||||||
@@ -282,7 +301,8 @@ export async function sendSlackMessage(
|
|||||||
message: fileMessage,
|
message: fileMessage,
|
||||||
ts: fileMessage.ts,
|
ts: fileMessage.ts,
|
||||||
channel,
|
channel,
|
||||||
fileCount: uploadedFileIds.length,
|
fileCount: fileIds.length,
|
||||||
|
files: uploadedFiles,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
|||||||
import type { Client, SFTPWrapper } from 'ssh2'
|
import type { Client, SFTPWrapper } from 'ssh2'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
|
||||||
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
|
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
|
||||||
|
|
||||||
const logger = createLogger('SSHDownloadFileAPI')
|
const logger = createLogger('SSHDownloadFileAPI')
|
||||||
@@ -96,6 +97,8 @@ export async function POST(request: NextRequest) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const fileName = path.basename(remotePath)
|
const fileName = path.basename(remotePath)
|
||||||
|
const extension = getFileExtension(fileName)
|
||||||
|
const mimeType = getMimeTypeFromExtension(extension)
|
||||||
|
|
||||||
// Encode content as base64 for binary safety
|
// Encode content as base64 for binary safety
|
||||||
const base64Content = content.toString('base64')
|
const base64Content = content.toString('base64')
|
||||||
@@ -104,6 +107,12 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
|
file: {
|
||||||
|
name: fileName,
|
||||||
|
mimeType,
|
||||||
|
data: base64Content,
|
||||||
|
size: stats.size,
|
||||||
|
},
|
||||||
content: base64Content,
|
content: base64Content,
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
remotePath: remotePath,
|
remotePath: remotePath,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { env } from '@/lib/core/config/env'
|
import { env } from '@/lib/core/config/env'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils'
|
import { ensureZodObject, normalizeUrl } from '@/app/api/tools/stagehand/utils'
|
||||||
|
|
||||||
const logger = createLogger('StagehandExtractAPI')
|
const logger = createLogger('StagehandExtractAPI')
|
||||||
@@ -120,7 +121,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const page = stagehand.context.pages()[0]
|
const page = stagehand.context.pages()[0]
|
||||||
|
|
||||||
logger.info(`Navigating to ${url}`)
|
logger.info(`Navigating to ${sanitizeUrlForLog(url)}`)
|
||||||
await page.goto(url, { waitUntil: 'networkidle' })
|
await page.goto(url, { waitUntil: 'networkidle' })
|
||||||
logger.info('Navigation complete')
|
logger.info('Navigation complete')
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { extractAudioFromVideo, isVideoFile } from '@/lib/audio/extractor'
|
import { extractAudioFromVideo, isVideoFile } from '@/lib/audio/extractor'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { TranscriptSegment } from '@/tools/stt/types'
|
import type { TranscriptSegment } from '@/tools/stt/types'
|
||||||
@@ -88,7 +89,7 @@ export async function POST(request: NextRequest) {
|
|||||||
audioFileName = file.name
|
audioFileName = file.name
|
||||||
audioMimeType = file.type
|
audioMimeType = file.type
|
||||||
} else if (body.audioUrl) {
|
} else if (body.audioUrl) {
|
||||||
logger.info(`[${requestId}] Downloading from URL: ${body.audioUrl}`)
|
logger.info(`[${requestId}] Downloading from URL: ${sanitizeUrlForLog(body.audioUrl)}`)
|
||||||
|
|
||||||
const response = await fetch(body.audioUrl)
|
const response = await fetch(body.audioUrl)
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ export async function POST(request: NextRequest) {
|
|||||||
logger.info(`[${requestId}] Uploading document: ${userFile.name}`)
|
logger.info(`[${requestId}] Uploading document: ${userFile.name}`)
|
||||||
|
|
||||||
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
|
||||||
|
const filesOutput = [
|
||||||
|
{
|
||||||
|
name: userFile.name,
|
||||||
|
mimeType: userFile.type || 'application/octet-stream',
|
||||||
|
data: buffer.toString('base64'),
|
||||||
|
size: buffer.length,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
logger.info(`[${requestId}] Downloaded file: ${buffer.length} bytes`)
|
logger.info(`[${requestId}] Downloaded file: ${buffer.length} bytes`)
|
||||||
|
|
||||||
@@ -136,6 +144,7 @@ export async function POST(request: NextRequest) {
|
|||||||
output: {
|
output: {
|
||||||
message: 'Document sent successfully',
|
message: 'Document sent successfully',
|
||||||
data: data.result,
|
data: data.result,
|
||||||
|
files: filesOutput,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { ArrowDown, Loader2 } from 'lucide-react'
|
import { ArrowDown, Loader2 } from 'lucide-react'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { Button } from '@/components/emcn'
|
import { Button } from '@/components/emcn'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { extractWorkspaceIdFromExecutionKey, getViewerUrl } from '@/lib/uploads/utils/file-utils'
|
import { extractWorkspaceIdFromExecutionKey, getViewerUrl } from '@/lib/uploads/utils/file-utils'
|
||||||
|
|
||||||
const logger = createLogger('FileCards')
|
const logger = createLogger('FileCards')
|
||||||
@@ -57,7 +58,7 @@ function FileCard({ file, isExecutionFile = false, workspaceId }: FileCardProps)
|
|||||||
if (file.key.startsWith('url/')) {
|
if (file.key.startsWith('url/')) {
|
||||||
if (file.url) {
|
if (file.url) {
|
||||||
window.open(file.url, '_blank')
|
window.open(file.url, '_blank')
|
||||||
logger.info(`Opened URL-type file directly: ${file.url}`)
|
logger.info(`Opened URL-type file directly: ${sanitizeUrlForLog(file.url)}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
throw new Error('URL is required for URL-type files')
|
throw new Error('URL is required for URL-type files')
|
||||||
@@ -77,13 +78,13 @@ function FileCard({ file, isExecutionFile = false, workspaceId }: FileCardProps)
|
|||||||
const serveUrl =
|
const serveUrl =
|
||||||
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
file.url || `/api/files/serve/${encodeURIComponent(file.key)}?context=execution`
|
||||||
window.open(serveUrl, '_blank')
|
window.open(serveUrl, '_blank')
|
||||||
logger.info(`Opened execution file serve URL: ${serveUrl}`)
|
logger.info(`Opened execution file serve URL: ${sanitizeUrlForLog(serveUrl)}`)
|
||||||
} else {
|
} else {
|
||||||
const viewerUrl = resolvedWorkspaceId ? getViewerUrl(file.key, resolvedWorkspaceId) : null
|
const viewerUrl = resolvedWorkspaceId ? getViewerUrl(file.key, resolvedWorkspaceId) : null
|
||||||
|
|
||||||
if (viewerUrl) {
|
if (viewerUrl) {
|
||||||
router.push(viewerUrl)
|
router.push(viewerUrl)
|
||||||
logger.info(`Navigated to viewer URL: ${viewerUrl}`)
|
logger.info(`Navigated to viewer URL: ${sanitizeUrlForLog(viewerUrl)}`)
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
`Could not construct viewer URL for file: ${file.name}, falling back to serve URL`
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ export function Chat() {
|
|||||||
|
|
||||||
const newReservedFields: StartInputFormatField[] = missingStartReservedFields.map(
|
const newReservedFields: StartInputFormatField[] = missingStartReservedFields.map(
|
||||||
(fieldName) => {
|
(fieldName) => {
|
||||||
const defaultType = fieldName === 'files' ? 'files' : 'string'
|
const defaultType = fieldName === 'files' ? 'file[]' : 'string'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ const getOutputTypeForPath = (
|
|||||||
const chatModeTypes: Record<string, string> = {
|
const chatModeTypes: Record<string, string> = {
|
||||||
input: 'string',
|
input: 'string',
|
||||||
conversationId: 'string',
|
conversationId: 'string',
|
||||||
files: 'files',
|
files: 'file[]',
|
||||||
}
|
}
|
||||||
return chatModeTypes[outputPath] || 'any'
|
return chatModeTypes[outputPath] || 'any'
|
||||||
}
|
}
|
||||||
@@ -1563,16 +1563,11 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
|
|||||||
blockTagGroups.sort((a, b) => a.distance - b.distance)
|
blockTagGroups.sort((a, b) => a.distance - b.distance)
|
||||||
finalBlockTagGroups.push(...blockTagGroups)
|
finalBlockTagGroups.push(...blockTagGroups)
|
||||||
|
|
||||||
const contextualTags: string[] = []
|
const groupTags = finalBlockTagGroups.flatMap((group) => group.tags)
|
||||||
if (loopBlockGroup) {
|
const tags = [...groupTags, ...variableTags]
|
||||||
contextualTags.push(...loopBlockGroup.tags)
|
|
||||||
}
|
|
||||||
if (parallelBlockGroup) {
|
|
||||||
contextualTags.push(...parallelBlockGroup.tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tags: [...allBlockTags, ...variableTags, ...contextualTags],
|
tags,
|
||||||
variableInfoMap,
|
variableInfoMap,
|
||||||
blockTagGroups: finalBlockTagGroups,
|
blockTagGroups: finalBlockTagGroups,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -578,13 +578,20 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
|
|||||||
if (!params.serverId) throw new Error('Server ID is required')
|
if (!params.serverId) throw new Error('Server ID is required')
|
||||||
|
|
||||||
switch (params.operation) {
|
switch (params.operation) {
|
||||||
case 'discord_send_message':
|
case 'discord_send_message': {
|
||||||
|
const fileParam = params.attachmentFiles || params.files
|
||||||
|
const normalizedFiles = fileParam
|
||||||
|
? Array.isArray(fileParam)
|
||||||
|
? fileParam
|
||||||
|
: [fileParam]
|
||||||
|
: undefined
|
||||||
return {
|
return {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
channelId: params.channelId,
|
channelId: params.channelId,
|
||||||
content: params.content,
|
content: params.content,
|
||||||
files: params.attachmentFiles || params.files,
|
files: normalizedFiles,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case 'discord_get_messages':
|
case 'discord_get_messages':
|
||||||
return {
|
return {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
@@ -789,6 +796,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
|
|||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
message: { type: 'string', description: 'Status message' },
|
message: { type: 'string', description: 'Status message' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
data: { type: 'json', description: 'Response data' },
|
data: { type: 'json', description: 'Response data' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,5 +73,6 @@ export const ElevenLabsBlock: BlockConfig<ElevenLabsBlockResponse> = {
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
audioUrl: { type: 'string', description: 'Generated audio URL' },
|
audioUrl: { type: 'string', description: 'Generated audio URL' },
|
||||||
|
audioFile: { type: 'file', description: 'Generated audio file' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
|||||||
{ label: 'Update Comment', id: 'update_comment' },
|
{ label: 'Update Comment', id: 'update_comment' },
|
||||||
{ label: 'Delete Comment', id: 'delete_comment' },
|
{ label: 'Delete Comment', id: 'delete_comment' },
|
||||||
{ label: 'Get Attachments', id: 'get_attachments' },
|
{ label: 'Get Attachments', id: 'get_attachments' },
|
||||||
|
{ label: 'Add Attachment', id: 'add_attachment' },
|
||||||
{ label: 'Delete Attachment', id: 'delete_attachment' },
|
{ label: 'Delete Attachment', id: 'delete_attachment' },
|
||||||
{ label: 'Add Worklog', id: 'add_worklog' },
|
{ label: 'Add Worklog', id: 'add_worklog' },
|
||||||
{ label: 'Get Worklogs', id: 'get_worklogs' },
|
{ label: 'Get Worklogs', id: 'get_worklogs' },
|
||||||
@@ -137,6 +138,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
|||||||
'update_comment',
|
'update_comment',
|
||||||
'delete_comment',
|
'delete_comment',
|
||||||
'get_attachments',
|
'get_attachments',
|
||||||
|
'add_attachment',
|
||||||
'add_worklog',
|
'add_worklog',
|
||||||
'get_worklogs',
|
'get_worklogs',
|
||||||
'update_worklog',
|
'update_worklog',
|
||||||
@@ -168,6 +170,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
|||||||
'update_comment',
|
'update_comment',
|
||||||
'delete_comment',
|
'delete_comment',
|
||||||
'get_attachments',
|
'get_attachments',
|
||||||
|
'add_attachment',
|
||||||
'add_worklog',
|
'add_worklog',
|
||||||
'get_worklogs',
|
'get_worklogs',
|
||||||
'update_worklog',
|
'update_worklog',
|
||||||
@@ -407,6 +410,27 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
condition: { field: 'operation', value: ['update_comment', 'delete_comment'] },
|
condition: { field: 'operation', value: ['update_comment', 'delete_comment'] },
|
||||||
},
|
},
|
||||||
// Attachment fields
|
// Attachment fields
|
||||||
|
{
|
||||||
|
id: 'attachmentFiles',
|
||||||
|
title: 'Attachments',
|
||||||
|
type: 'file-upload',
|
||||||
|
canonicalParamId: 'files',
|
||||||
|
placeholder: 'Upload files',
|
||||||
|
condition: { field: 'operation', value: 'add_attachment' },
|
||||||
|
mode: 'basic',
|
||||||
|
multiple: true,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'files',
|
||||||
|
title: 'File References',
|
||||||
|
type: 'short-input',
|
||||||
|
canonicalParamId: 'files',
|
||||||
|
placeholder: 'File reference from previous block',
|
||||||
|
condition: { field: 'operation', value: 'add_attachment' },
|
||||||
|
mode: 'advanced',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'attachmentId',
|
id: 'attachmentId',
|
||||||
title: 'Attachment ID',
|
title: 'Attachment ID',
|
||||||
@@ -576,6 +600,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
'jira_update_comment',
|
'jira_update_comment',
|
||||||
'jira_delete_comment',
|
'jira_delete_comment',
|
||||||
'jira_get_attachments',
|
'jira_get_attachments',
|
||||||
|
'jira_add_attachment',
|
||||||
'jira_delete_attachment',
|
'jira_delete_attachment',
|
||||||
'jira_add_worklog',
|
'jira_add_worklog',
|
||||||
'jira_get_worklogs',
|
'jira_get_worklogs',
|
||||||
@@ -623,6 +648,8 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
return 'jira_delete_comment'
|
return 'jira_delete_comment'
|
||||||
case 'get_attachments':
|
case 'get_attachments':
|
||||||
return 'jira_get_attachments'
|
return 'jira_get_attachments'
|
||||||
|
case 'add_attachment':
|
||||||
|
return 'jira_add_attachment'
|
||||||
case 'delete_attachment':
|
case 'delete_attachment':
|
||||||
return 'jira_delete_attachment'
|
return 'jira_delete_attachment'
|
||||||
case 'add_worklog':
|
case 'add_worklog':
|
||||||
@@ -838,6 +865,21 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
issueKey: effectiveIssueKey,
|
issueKey: effectiveIssueKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'add_attachment': {
|
||||||
|
if (!effectiveIssueKey) {
|
||||||
|
throw new Error('Issue Key is required to add attachments.')
|
||||||
|
}
|
||||||
|
const fileParam = params.attachmentFiles || params.files
|
||||||
|
if (!fileParam || (Array.isArray(fileParam) && fileParam.length === 0)) {
|
||||||
|
throw new Error('At least one attachment file is required.')
|
||||||
|
}
|
||||||
|
const normalizedFiles = Array.isArray(fileParam) ? fileParam : [fileParam]
|
||||||
|
return {
|
||||||
|
...baseParams,
|
||||||
|
issueKey: effectiveIssueKey,
|
||||||
|
files: normalizedFiles,
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'delete_attachment': {
|
case 'delete_attachment': {
|
||||||
return {
|
return {
|
||||||
...baseParams,
|
...baseParams,
|
||||||
@@ -982,6 +1024,8 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
commentBody: { type: 'string', description: 'Text content for comment operations' },
|
commentBody: { type: 'string', description: 'Text content for comment operations' },
|
||||||
commentId: { type: 'string', description: 'Comment ID for update/delete operations' },
|
commentId: { type: 'string', description: 'Comment ID for update/delete operations' },
|
||||||
// Attachment operation inputs
|
// Attachment operation inputs
|
||||||
|
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
|
||||||
|
files: { type: 'array', description: 'Files to attach (UserFile array)' },
|
||||||
attachmentId: { type: 'string', description: 'Attachment ID for delete operation' },
|
attachmentId: { type: 'string', description: 'Attachment ID for delete operation' },
|
||||||
// Worklog operation inputs
|
// Worklog operation inputs
|
||||||
timeSpentSeconds: {
|
timeSpentSeconds: {
|
||||||
@@ -1049,9 +1093,11 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
|
|
||||||
// jira_get_attachments outputs
|
// jira_get_attachments outputs
|
||||||
attachments: {
|
attachments: {
|
||||||
type: 'file[]',
|
type: 'json',
|
||||||
description: 'Array of attachments with id, filename, size, mimeType, created, author',
|
description: 'Array of attachments with id, filename, size, mimeType, created, author',
|
||||||
},
|
},
|
||||||
|
files: { type: 'file[]', description: 'Uploaded attachment files' },
|
||||||
|
attachmentIds: { type: 'json', description: 'Uploaded attachment IDs' },
|
||||||
|
|
||||||
// jira_delete_attachment, jira_delete_comment, jira_delete_issue, jira_delete_worklog, jira_delete_issue_link outputs
|
// jira_delete_attachment, jira_delete_comment, jira_delete_issue, jira_delete_worklog, jira_delete_issue_link outputs
|
||||||
attachmentId: { type: 'string', description: 'Deleted attachment ID' },
|
attachmentId: { type: 'string', description: 'Deleted attachment ID' },
|
||||||
|
|||||||
@@ -668,17 +668,44 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
|||||||
generationType: 'timestamp',
|
generationType: 'timestamp',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Attachment file
|
||||||
|
{
|
||||||
|
id: 'attachmentFileUpload',
|
||||||
|
title: 'Attachment',
|
||||||
|
type: 'file-upload',
|
||||||
|
canonicalParamId: 'file',
|
||||||
|
placeholder: 'Upload attachment',
|
||||||
|
condition: {
|
||||||
|
field: 'operation',
|
||||||
|
value: ['linear_create_attachment'],
|
||||||
|
},
|
||||||
|
mode: 'basic',
|
||||||
|
multiple: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'file',
|
||||||
|
title: 'File Reference',
|
||||||
|
type: 'short-input',
|
||||||
|
canonicalParamId: 'file',
|
||||||
|
placeholder: 'File reference from previous block',
|
||||||
|
condition: {
|
||||||
|
field: 'operation',
|
||||||
|
value: ['linear_create_attachment'],
|
||||||
|
},
|
||||||
|
mode: 'advanced',
|
||||||
|
},
|
||||||
// Attachment URL
|
// Attachment URL
|
||||||
{
|
{
|
||||||
id: 'url',
|
id: 'url',
|
||||||
title: 'URL',
|
title: 'URL',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter URL',
|
placeholder: 'Enter URL',
|
||||||
required: true,
|
required: false,
|
||||||
condition: {
|
condition: {
|
||||||
field: 'operation',
|
field: 'operation',
|
||||||
value: ['linear_create_attachment'],
|
value: ['linear_create_attachment'],
|
||||||
},
|
},
|
||||||
|
mode: 'advanced',
|
||||||
},
|
},
|
||||||
// Attachment title
|
// Attachment title
|
||||||
{
|
{
|
||||||
@@ -1742,16 +1769,31 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
|||||||
teamId: effectiveTeamId,
|
teamId: effectiveTeamId,
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'linear_create_attachment':
|
case 'linear_create_attachment': {
|
||||||
if (!params.issueId?.trim() || !params.url?.trim()) {
|
if (!params.issueId?.trim()) {
|
||||||
throw new Error('Issue ID and URL are required.')
|
throw new Error('Issue ID is required.')
|
||||||
|
}
|
||||||
|
if (Array.isArray(params.file)) {
|
||||||
|
throw new Error('Attachment file must be a single file.')
|
||||||
|
}
|
||||||
|
if (Array.isArray(params.attachmentFileUpload)) {
|
||||||
|
throw new Error('Attachment file must be a single file.')
|
||||||
|
}
|
||||||
|
const attachmentFile = params.attachmentFileUpload || params.file
|
||||||
|
const attachmentUrl =
|
||||||
|
params.url?.trim() ||
|
||||||
|
(attachmentFile && !Array.isArray(attachmentFile) ? attachmentFile.url : undefined)
|
||||||
|
if (!attachmentUrl) {
|
||||||
|
throw new Error('URL or file is required.')
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...baseParams,
|
...baseParams,
|
||||||
issueId: params.issueId.trim(),
|
issueId: params.issueId.trim(),
|
||||||
url: params.url.trim(),
|
url: attachmentUrl,
|
||||||
|
file: attachmentFile,
|
||||||
title: params.attachmentTitle,
|
title: params.attachmentTitle,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case 'linear_list_attachments':
|
case 'linear_list_attachments':
|
||||||
if (!params.issueId?.trim()) {
|
if (!params.issueId?.trim()) {
|
||||||
@@ -2248,6 +2290,8 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
|||||||
endDate: { type: 'string', description: 'End date' },
|
endDate: { type: 'string', description: 'End date' },
|
||||||
targetDate: { type: 'string', description: 'Target date' },
|
targetDate: { type: 'string', description: 'Target date' },
|
||||||
url: { type: 'string', description: 'URL' },
|
url: { type: 'string', description: 'URL' },
|
||||||
|
attachmentFileUpload: { type: 'json', description: 'File to attach (UI upload)' },
|
||||||
|
file: { type: 'json', description: 'File to attach (UserFile)' },
|
||||||
attachmentTitle: { type: 'string', description: 'Attachment title' },
|
attachmentTitle: { type: 'string', description: 'Attachment title' },
|
||||||
attachmentId: { type: 'string', description: 'Attachment identifier' },
|
attachmentId: { type: 'string', description: 'Attachment identifier' },
|
||||||
relationType: { type: 'string', description: 'Relation type' },
|
relationType: { type: 'string', description: 'Relation type' },
|
||||||
@@ -2341,7 +2385,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
|||||||
cycles: { type: 'json', description: 'Cycles list' },
|
cycles: { type: 'json', description: 'Cycles list' },
|
||||||
// Attachment outputs
|
// Attachment outputs
|
||||||
attachment: { type: 'json', description: 'Attachment data' },
|
attachment: { type: 'json', description: 'Attachment data' },
|
||||||
attachments: { type: 'file[]', description: 'Attachments list' },
|
attachments: { type: 'json', description: 'Attachments list' },
|
||||||
// Relation outputs
|
// Relation outputs
|
||||||
relation: { type: 'json', description: 'Issue relation data' },
|
relation: { type: 'json', description: 'Issue relation data' },
|
||||||
relations: { type: 'json', description: 'Issue relations list' },
|
relations: { type: 'json', description: 'Issue relations list' },
|
||||||
|
|||||||
@@ -346,7 +346,10 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
|||||||
// Add files if provided
|
// Add files if provided
|
||||||
const fileParam = attachmentFiles || files
|
const fileParam = attachmentFiles || files
|
||||||
if (fileParam && (operation === 'write_chat' || operation === 'write_channel')) {
|
if (fileParam && (operation === 'write_chat' || operation === 'write_channel')) {
|
||||||
baseParams.files = fileParam
|
const normalizedFiles = Array.isArray(fileParam) ? fileParam : [fileParam]
|
||||||
|
if (normalizedFiles.length > 0) {
|
||||||
|
baseParams.files = normalizedFiles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add messageId if provided
|
// Add messageId if provided
|
||||||
@@ -463,6 +466,7 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
|||||||
totalAttachments: { type: 'number', description: 'Total number of attachments' },
|
totalAttachments: { type: 'number', description: 'Total number of attachments' },
|
||||||
attachmentTypes: { type: 'json', description: 'Array of attachment content types' },
|
attachmentTypes: { type: 'json', description: 'Array of attachment content types' },
|
||||||
attachments: { type: 'file[]', description: 'Downloaded message attachments' },
|
attachments: { type: 'file[]', description: 'Downloaded message attachments' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
updatedContent: {
|
updatedContent: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether content was successfully updated/sent',
|
description: 'Whether content was successfully updated/sent',
|
||||||
|
|||||||
@@ -803,7 +803,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
|||||||
outputs: {
|
outputs: {
|
||||||
deals: { type: 'json', description: 'Array of deal objects' },
|
deals: { type: 'json', description: 'Array of deal objects' },
|
||||||
deal: { type: 'json', description: 'Single deal object' },
|
deal: { type: 'json', description: 'Single deal object' },
|
||||||
files: { type: 'file[]', description: 'Array of file objects' },
|
files: { type: 'json', description: 'Array of file objects' },
|
||||||
messages: { type: 'json', description: 'Array of mail message objects' },
|
messages: { type: 'json', description: 'Array of mail message objects' },
|
||||||
pipelines: { type: 'json', description: 'Array of pipeline objects' },
|
pipelines: { type: 'json', description: 'Array of pipeline objects' },
|
||||||
projects: { type: 'json', description: 'Array of project objects' },
|
projects: { type: 'json', description: 'Array of project objects' },
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ export const SftpBlock: BlockConfig<SftpUploadResult> = {
|
|||||||
outputs: {
|
outputs: {
|
||||||
success: { type: 'boolean', description: 'Whether the operation was successful' },
|
success: { type: 'boolean', description: 'Whether the operation was successful' },
|
||||||
uploadedFiles: { type: 'json', description: 'Array of uploaded file details' },
|
uploadedFiles: { type: 'json', description: 'Array of uploaded file details' },
|
||||||
|
file: { type: 'file', description: 'Downloaded file stored in execution files' },
|
||||||
fileName: { type: 'string', description: 'Downloaded file name' },
|
fileName: { type: 'string', description: 'Downloaded file name' },
|
||||||
content: { type: 'string', description: 'Downloaded file content' },
|
content: { type: 'string', description: 'Downloaded file content' },
|
||||||
size: { type: 'number', description: 'File size in bytes' },
|
size: { type: 'number', description: 'File size in bytes' },
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
|
|||||||
description: 'Array of SharePoint list items with fields',
|
description: 'Array of SharePoint list items with fields',
|
||||||
},
|
},
|
||||||
uploadedFiles: {
|
uploadedFiles: {
|
||||||
type: 'file[]',
|
type: 'json',
|
||||||
description: 'Array of uploaded file objects with id, name, webUrl, size',
|
description: 'Array of uploaded file objects with id, name, webUrl, size',
|
||||||
},
|
},
|
||||||
fileCount: {
|
fileCount: {
|
||||||
|
|||||||
@@ -622,7 +622,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
|||||||
}
|
}
|
||||||
const fileParam = attachmentFiles || files
|
const fileParam = attachmentFiles || files
|
||||||
if (fileParam) {
|
if (fileParam) {
|
||||||
baseParams.files = fileParam
|
const normalizedFiles = Array.isArray(fileParam) ? fileParam : [fileParam]
|
||||||
|
if (normalizedFiles.length > 0) {
|
||||||
|
baseParams.files = normalizedFiles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -796,6 +799,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Number of files uploaded (when files are attached)',
|
description: 'Number of files uploaded (when files are attached)',
|
||||||
},
|
},
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
|
|
||||||
// slack_canvas outputs
|
// slack_canvas outputs
|
||||||
canvas_id: { type: 'string', description: 'Canvas identifier for created canvases' },
|
canvas_id: { type: 'string', description: 'Canvas identifier for created canvases' },
|
||||||
|
|||||||
@@ -507,6 +507,7 @@ export const SSHBlock: BlockConfig<SSHResponse> = {
|
|||||||
stderr: { type: 'string', description: 'Command standard error' },
|
stderr: { type: 'string', description: 'Command standard error' },
|
||||||
exitCode: { type: 'number', description: 'Command exit code' },
|
exitCode: { type: 'number', description: 'Command exit code' },
|
||||||
success: { type: 'boolean', description: 'Operation success status' },
|
success: { type: 'boolean', description: 'Operation success status' },
|
||||||
|
file: { type: 'file', description: 'Downloaded file stored in execution files' },
|
||||||
fileContent: { type: 'string', description: 'Downloaded/read file content' },
|
fileContent: { type: 'string', description: 'Downloaded/read file content' },
|
||||||
entries: { type: 'json', description: 'Directory entries' },
|
entries: { type: 'json', description: 'Directory entries' },
|
||||||
exists: { type: 'boolean', description: 'File/directory existence' },
|
exists: { type: 'boolean', description: 'File/directory existence' },
|
||||||
|
|||||||
@@ -314,9 +314,14 @@ export const TelegramBlock: BlockConfig<TelegramResponse> = {
|
|||||||
case 'telegram_send_document': {
|
case 'telegram_send_document': {
|
||||||
// Handle file upload
|
// Handle file upload
|
||||||
const fileParam = params.attachmentFiles || params.files
|
const fileParam = params.attachmentFiles || params.files
|
||||||
|
const normalizedFiles = fileParam
|
||||||
|
? Array.isArray(fileParam)
|
||||||
|
? fileParam
|
||||||
|
: [fileParam]
|
||||||
|
: undefined
|
||||||
return {
|
return {
|
||||||
...commonParams,
|
...commonParams,
|
||||||
files: fileParam,
|
files: normalizedFiles,
|
||||||
caption: params.caption,
|
caption: params.caption,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,6 +364,7 @@ export const TelegramBlock: BlockConfig<TelegramResponse> = {
|
|||||||
},
|
},
|
||||||
message: { type: 'string', description: 'Success or error message' },
|
message: { type: 'string', description: 'Success or error message' },
|
||||||
data: { type: 'json', description: 'Response data' },
|
data: { type: 'json', description: 'Response data' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
// Specific result fields
|
// Specific result fields
|
||||||
messageId: { type: 'number', description: 'Sent message ID' },
|
messageId: { type: 'number', description: 'Sent message ID' },
|
||||||
chatId: { type: 'number', description: 'Chat ID where message was sent' },
|
chatId: { type: 'number', description: 'Chat ID where message was sent' },
|
||||||
|
|||||||
19
apps/sim/lib/core/utils/logging.ts
Normal file
19
apps/sim/lib/core/utils/logging.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Sanitize URLs for logging by stripping query/hash and truncating.
|
||||||
|
*/
|
||||||
|
export function sanitizeUrlForLog(url: string, maxLength = 120): string {
|
||||||
|
if (!url) return ''
|
||||||
|
|
||||||
|
const trimmed = url.trim()
|
||||||
|
try {
|
||||||
|
const hasProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(trimmed)
|
||||||
|
const parsed = new URL(trimmed, hasProtocol ? undefined : 'http://localhost')
|
||||||
|
const origin = parsed.origin === 'null' ? '' : parsed.origin
|
||||||
|
const sanitized = `${origin}${parsed.pathname}`
|
||||||
|
const result = sanitized || parsed.pathname || trimmed
|
||||||
|
return result.length > maxLength ? `${result.slice(0, maxLength)}...` : result
|
||||||
|
} catch {
|
||||||
|
const withoutQuery = trimmed.split('?')[0].split('#')[0]
|
||||||
|
return withoutQuery.length > maxLength ? `${withoutQuery.slice(0, maxLength)}...` : withoutQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { PDFDocument } from 'pdf-lib'
|
|||||||
import { getBYOKKey } from '@/lib/api-key/byok'
|
import { getBYOKKey } from '@/lib/api-key/byok'
|
||||||
import { type Chunk, JsonYamlChunker, StructuredDataChunker, TextChunker } from '@/lib/chunkers'
|
import { type Chunk, JsonYamlChunker, StructuredDataChunker, TextChunker } from '@/lib/chunkers'
|
||||||
import { env } from '@/lib/core/config/env'
|
import { env } from '@/lib/core/config/env'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import { parseBuffer, parseFile } from '@/lib/file-parsers'
|
import { parseBuffer, parseFile } from '@/lib/file-parsers'
|
||||||
import type { FileParseMetadata } from '@/lib/file-parsers/types'
|
import type { FileParseMetadata } from '@/lib/file-parsers/types'
|
||||||
import { retryWithExponentialBackoff } from '@/lib/knowledge/documents/utils'
|
import { retryWithExponentialBackoff } from '@/lib/knowledge/documents/utils'
|
||||||
@@ -489,7 +490,7 @@ async function parseWithMistralOCR(
|
|||||||
workspaceId
|
workspaceId
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(`Mistral OCR: Using presigned URL for ${filename}: ${httpsUrl.substring(0, 120)}...`)
|
logger.info(`Mistral OCR: Using presigned URL for ${filename}: ${sanitizeUrlForLog(httpsUrl)}`)
|
||||||
|
|
||||||
let pageCount = 0
|
let pageCount = 0
|
||||||
if (mimeType === 'application/pdf' && buffer) {
|
if (mimeType === 'application/pdf' && buffer) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
secureFetchWithPinnedIP,
|
secureFetchWithPinnedIP,
|
||||||
validateUrlWithDNS,
|
validateUrlWithDNS,
|
||||||
} from '@/lib/core/security/input-validation'
|
} from '@/lib/core/security/input-validation'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import type { DbOrTx } from '@/lib/db/types'
|
import type { DbOrTx } from '@/lib/db/types'
|
||||||
import { getProviderIdFromServiceId } from '@/lib/oauth'
|
import { getProviderIdFromServiceId } from '@/lib/oauth'
|
||||||
import {
|
import {
|
||||||
@@ -114,7 +115,7 @@ async function fetchWithDNSPinning(
|
|||||||
const urlValidation = await validateUrlWithDNS(url, 'contentUrl')
|
const urlValidation = await validateUrlWithDNS(url, 'contentUrl')
|
||||||
if (!urlValidation.isValid) {
|
if (!urlValidation.isValid) {
|
||||||
logger.warn(`[${requestId}] Invalid content URL: ${urlValidation.error}`, {
|
logger.warn(`[${requestId}] Invalid content URL: ${urlValidation.error}`, {
|
||||||
url: url.substring(0, 100),
|
url: sanitizeUrlForLog(url),
|
||||||
})
|
})
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ async function fetchWithDNSPinning(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[${requestId}] Error fetching URL with DNS pinning`, {
|
logger.error(`[${requestId}] Error fetching URL with DNS pinning`, {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
url: url.substring(0, 100),
|
url: sanitizeUrlForLog(url),
|
||||||
})
|
})
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
|
import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
|
||||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
@@ -183,7 +184,7 @@ async function pollForCompletion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!liveUrlLogged && taskData.live_url) {
|
if (!liveUrlLogged && taskData.live_url) {
|
||||||
logger.info(`BrowserUse task ${taskId} live URL: ${taskData.live_url}`)
|
logger.info(`BrowserUse task ${taskId} live URL: ${sanitizeUrlForLog(taskData.live_url)}`)
|
||||||
liveUrlLogged = true
|
liveUrlLogged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export const discordSendMessageTool: ToolConfig<
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
message: { type: 'string', description: 'Success or error message' },
|
message: { type: 'string', description: 'Success or error message' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: 'Discord message data',
|
description: 'Discord message data',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
|
import type { ToolFileData } from '@/tools/types'
|
||||||
|
|
||||||
export interface DiscordMessage {
|
export interface DiscordMessage {
|
||||||
id: string
|
id: string
|
||||||
@@ -85,6 +86,7 @@ interface BaseDiscordResponse {
|
|||||||
export interface DiscordSendMessageResponse extends BaseDiscordResponse {
|
export interface DiscordSendMessageResponse extends BaseDiscordResponse {
|
||||||
output: {
|
output: {
|
||||||
message: string
|
message: string
|
||||||
|
files?: ToolFileData[]
|
||||||
data?: DiscordMessage
|
data?: DiscordMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
export interface ElevenLabsTtsParams {
|
export interface ElevenLabsTtsParams {
|
||||||
@@ -12,11 +13,13 @@ export interface ElevenLabsTtsParams {
|
|||||||
export interface ElevenLabsTtsResponse extends ToolResponse {
|
export interface ElevenLabsTtsResponse extends ToolResponse {
|
||||||
output: {
|
output: {
|
||||||
audioUrl: string
|
audioUrl: string
|
||||||
|
audioFile?: UserFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElevenLabsBlockResponse extends ToolResponse {
|
export interface ElevenLabsBlockResponse extends ToolResponse {
|
||||||
output: {
|
output: {
|
||||||
audioUrl: string
|
audioUrl: string
|
||||||
|
audioFile?: UserFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
apps/sim/tools/jira/add_attachment.ts
Normal file
83
apps/sim/tools/jira/add_attachment.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type { JiraAddAttachmentParams, JiraAddAttachmentResponse } from '@/tools/jira/types'
|
||||||
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
|
export const jiraAddAttachmentTool: ToolConfig<JiraAddAttachmentParams, JiraAddAttachmentResponse> =
|
||||||
|
{
|
||||||
|
id: 'jira_add_attachment',
|
||||||
|
name: 'Jira Add Attachment',
|
||||||
|
description: 'Add attachments to a Jira issue',
|
||||||
|
version: '1.0.0',
|
||||||
|
|
||||||
|
oauth: {
|
||||||
|
required: true,
|
||||||
|
provider: 'jira',
|
||||||
|
},
|
||||||
|
|
||||||
|
params: {
|
||||||
|
accessToken: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
visibility: 'hidden',
|
||||||
|
description: 'OAuth access token for Jira',
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
visibility: 'user-only',
|
||||||
|
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||||
|
},
|
||||||
|
issueKey: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
visibility: 'user-or-llm',
|
||||||
|
description: 'Jira issue key to add attachments to (e.g., PROJ-123)',
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
type: 'file[]',
|
||||||
|
required: true,
|
||||||
|
visibility: 'user-only',
|
||||||
|
description: 'Files to attach to the Jira issue',
|
||||||
|
},
|
||||||
|
cloudId: {
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
visibility: 'hidden',
|
||||||
|
description:
|
||||||
|
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
request: {
|
||||||
|
url: '/api/tools/jira/add-attachment',
|
||||||
|
method: 'POST',
|
||||||
|
headers: () => ({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}),
|
||||||
|
body: (params: JiraAddAttachmentParams) => ({
|
||||||
|
accessToken: params.accessToken,
|
||||||
|
domain: params.domain,
|
||||||
|
issueKey: params.issueKey,
|
||||||
|
files: params.files,
|
||||||
|
cloudId: params.cloudId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
transformResponse: async (response: Response) => {
|
||||||
|
const data = await response.json()
|
||||||
|
if (!response.ok || !data.success) {
|
||||||
|
throw new Error(data.error || 'Failed to add Jira attachment')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: data.output,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
outputs: {
|
||||||
|
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||||
|
issueKey: { type: 'string', description: 'Issue key' },
|
||||||
|
attachmentIds: { type: 'json', description: 'IDs of uploaded attachments' },
|
||||||
|
files: { type: 'file[]', description: 'Uploaded attachment files' },
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { jiraAddAttachmentTool } from '@/tools/jira/add_attachment'
|
||||||
import { jiraAddCommentTool } from '@/tools/jira/add_comment'
|
import { jiraAddCommentTool } from '@/tools/jira/add_comment'
|
||||||
import { jiraAddWatcherTool } from '@/tools/jira/add_watcher'
|
import { jiraAddWatcherTool } from '@/tools/jira/add_watcher'
|
||||||
import { jiraAddWorklogTool } from '@/tools/jira/add_worklog'
|
import { jiraAddWorklogTool } from '@/tools/jira/add_worklog'
|
||||||
@@ -32,6 +33,7 @@ export {
|
|||||||
jiraTransitionIssueTool,
|
jiraTransitionIssueTool,
|
||||||
jiraSearchIssuesTool,
|
jiraSearchIssuesTool,
|
||||||
jiraAddCommentTool,
|
jiraAddCommentTool,
|
||||||
|
jiraAddAttachmentTool,
|
||||||
jiraGetCommentsTool,
|
jiraGetCommentsTool,
|
||||||
jiraUpdateCommentTool,
|
jiraUpdateCommentTool,
|
||||||
jiraDeleteCommentTool,
|
jiraDeleteCommentTool,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ToolResponse } from '@/tools/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
|
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
export interface JiraRetrieveParams {
|
export interface JiraRetrieveParams {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
@@ -312,6 +313,23 @@ export interface JiraDeleteAttachmentResponse extends ToolResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JiraAddAttachmentParams {
|
||||||
|
accessToken: string
|
||||||
|
domain: string
|
||||||
|
issueKey: string
|
||||||
|
files: UserFile[]
|
||||||
|
cloudId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JiraAddAttachmentResponse extends ToolResponse {
|
||||||
|
output: {
|
||||||
|
ts: string
|
||||||
|
issueKey: string
|
||||||
|
attachmentIds: string[]
|
||||||
|
files: ToolFileData[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Worklogs
|
// Worklogs
|
||||||
export interface JiraAddWorklogParams {
|
export interface JiraAddWorklogParams {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
@@ -482,6 +500,7 @@ export type JiraResponse =
|
|||||||
| JiraUpdateCommentResponse
|
| JiraUpdateCommentResponse
|
||||||
| JiraDeleteCommentResponse
|
| JiraDeleteCommentResponse
|
||||||
| JiraGetAttachmentsResponse
|
| JiraGetAttachmentsResponse
|
||||||
|
| JiraAddAttachmentResponse
|
||||||
| JiraDeleteAttachmentResponse
|
| JiraDeleteAttachmentResponse
|
||||||
| JiraAddWorklogResponse
|
| JiraAddWorklogResponse
|
||||||
| JiraGetWorklogsResponse
|
| JiraGetWorklogsResponse
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ export const linearCreateAttachmentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: false,
|
||||||
visibility: 'user-or-llm',
|
visibility: 'user-or-llm',
|
||||||
description: 'URL of the attachment',
|
description: 'URL of the attachment',
|
||||||
},
|
},
|
||||||
|
file: {
|
||||||
|
type: 'file',
|
||||||
|
required: false,
|
||||||
|
visibility: 'user-only',
|
||||||
|
description: 'File to attach',
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
@@ -59,9 +65,14 @@ export const linearCreateAttachmentTool: ToolConfig<
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
body: (params) => {
|
body: (params) => {
|
||||||
|
const attachmentUrl = params.url || params.file?.url
|
||||||
|
if (!attachmentUrl) {
|
||||||
|
throw new Error('URL or file is required')
|
||||||
|
}
|
||||||
|
|
||||||
const input: Record<string, any> = {
|
const input: Record<string, any> = {
|
||||||
issueId: params.issueId,
|
issueId: params.issueId,
|
||||||
url: params.url,
|
url: attachmentUrl,
|
||||||
title: params.title,
|
title: params.title,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -875,7 +876,8 @@ export interface LinearGetActiveCycleParams {
|
|||||||
|
|
||||||
export interface LinearCreateAttachmentParams {
|
export interface LinearCreateAttachmentParams {
|
||||||
issueId: string
|
issueId: string
|
||||||
url: string
|
url?: string
|
||||||
|
file?: UserFile
|
||||||
title?: string
|
title?: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import type {
|
import type {
|
||||||
MicrosoftPlannerReadResponse,
|
MicrosoftPlannerReadResponse,
|
||||||
MicrosoftPlannerToolParams,
|
MicrosoftPlannerToolParams,
|
||||||
@@ -76,7 +77,7 @@ export const readTaskTool: ToolConfig<MicrosoftPlannerToolParams, MicrosoftPlann
|
|||||||
finalUrl = 'https://graph.microsoft.com/v1.0/me/planner/tasks'
|
finalUrl = 'https://graph.microsoft.com/v1.0/me/planner/tasks'
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Microsoft Planner URL:', finalUrl)
|
logger.info('Microsoft Planner URL:', sanitizeUrlForLog(finalUrl))
|
||||||
return finalUrl
|
return finalUrl
|
||||||
},
|
},
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
export interface MicrosoftTeamsAttachment {
|
export interface MicrosoftTeamsAttachment {
|
||||||
id: string
|
id: string
|
||||||
@@ -61,6 +61,7 @@ export interface MicrosoftTeamsWriteResponse extends ToolResponse {
|
|||||||
output: {
|
output: {
|
||||||
updatedContent: boolean
|
updatedContent: boolean
|
||||||
metadata: MicrosoftTeamsMetadata
|
metadata: MicrosoftTeamsMetadata
|
||||||
|
files?: ToolFileData[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export const writeChannelTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTea
|
|||||||
createdTime: { type: 'string', description: 'Timestamp when message was created' },
|
createdTime: { type: 'string', description: 'Timestamp when message was created' },
|
||||||
url: { type: 'string', description: 'Web URL to the message' },
|
url: { type: 'string', description: 'Web URL to the message' },
|
||||||
updatedContent: { type: 'boolean', description: 'Whether content was successfully updated' },
|
updatedContent: { type: 'boolean', description: 'Whether content was successfully updated' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
},
|
},
|
||||||
|
|
||||||
request: {
|
request: {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const writeChatTool: ToolConfig<MicrosoftTeamsToolParams, MicrosoftTeamsW
|
|||||||
createdTime: { type: 'string', description: 'Timestamp when message was created' },
|
createdTime: { type: 'string', description: 'Timestamp when message was created' },
|
||||||
url: { type: 'string', description: 'Web URL to the message' },
|
url: { type: 'string', description: 'Web URL to the message' },
|
||||||
updatedContent: { type: 'boolean', description: 'Whether content was successfully updated' },
|
updatedContent: { type: 'boolean', description: 'Whether content was successfully updated' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
},
|
},
|
||||||
|
|
||||||
request: {
|
request: {
|
||||||
|
|||||||
@@ -98,10 +98,10 @@ export const mistralParserTool: ToolConfig<MistralParserInput, MistralParserOutp
|
|||||||
if (
|
if (
|
||||||
typeof params.fileUpload === 'object' &&
|
typeof params.fileUpload === 'object' &&
|
||||||
params.fileUpload !== null &&
|
params.fileUpload !== null &&
|
||||||
(params.fileUpload.url || params.fileUpload.path)
|
params.fileUpload.url
|
||||||
) {
|
) {
|
||||||
// Get the full URL to the file - prefer url over path for UserFile compatibility
|
// Get the full URL to the file
|
||||||
let uploadedFilePath = params.fileUpload.url || params.fileUpload.path
|
let uploadedFilePath = params.fileUpload.url
|
||||||
|
|
||||||
// Make sure the file path is an absolute URL
|
// Make sure the file path is an absolute URL
|
||||||
if (uploadedFilePath.startsWith('/')) {
|
if (uploadedFilePath.startsWith('/')) {
|
||||||
@@ -184,9 +184,9 @@ export const mistralParserTool: ToolConfig<MistralParserInput, MistralParserOutp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an internal workspace file path
|
// Check if this is an internal workspace file path
|
||||||
if (params.fileUpload?.path?.startsWith('/api/files/serve/')) {
|
if (params.fileUpload?.url?.startsWith('/api/files/serve/')) {
|
||||||
// Update filePath to the internal path for workspace files
|
// Update filePath to the internal path for workspace files
|
||||||
requestBody.filePath = params.fileUpload.path
|
requestBody.filePath = params.fileUpload.url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add optional parameters with proper validation
|
// Add optional parameters with proper validation
|
||||||
|
|||||||
@@ -763,6 +763,7 @@ import {
|
|||||||
} from '@/tools/intercom'
|
} from '@/tools/intercom'
|
||||||
import { jinaReadUrlTool, jinaSearchTool } from '@/tools/jina'
|
import { jinaReadUrlTool, jinaSearchTool } from '@/tools/jina'
|
||||||
import {
|
import {
|
||||||
|
jiraAddAttachmentTool,
|
||||||
jiraAddCommentTool,
|
jiraAddCommentTool,
|
||||||
jiraAddWatcherTool,
|
jiraAddWatcherTool,
|
||||||
jiraAddWorklogTool,
|
jiraAddWorklogTool,
|
||||||
@@ -1879,6 +1880,7 @@ export const tools: Record<string, ToolConfig> = {
|
|||||||
jira_update_comment: jiraUpdateCommentTool,
|
jira_update_comment: jiraUpdateCommentTool,
|
||||||
jira_delete_comment: jiraDeleteCommentTool,
|
jira_delete_comment: jiraDeleteCommentTool,
|
||||||
jira_get_attachments: jiraGetAttachmentsTool,
|
jira_get_attachments: jiraGetAttachmentsTool,
|
||||||
|
jira_add_attachment: jiraAddAttachmentTool,
|
||||||
jira_delete_attachment: jiraDeleteAttachmentTool,
|
jira_delete_attachment: jiraDeleteAttachmentTool,
|
||||||
jira_add_worklog: jiraAddWorklogTool,
|
jira_add_worklog: jiraAddWorklogTool,
|
||||||
jira_get_worklogs: jiraGetWorklogsTool,
|
jira_get_worklogs: jiraGetWorklogsTool,
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export const sftpDownloadTool: ToolConfig<SftpDownloadParams, SftpDownloadResult
|
|||||||
output: {
|
output: {
|
||||||
success: true,
|
success: true,
|
||||||
fileName: data.fileName,
|
fileName: data.fileName,
|
||||||
|
file: data.file,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
size: data.size,
|
size: data.size,
|
||||||
encoding: data.encoding,
|
encoding: data.encoding,
|
||||||
@@ -104,6 +105,7 @@ export const sftpDownloadTool: ToolConfig<SftpDownloadParams, SftpDownloadResult
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
success: { type: 'boolean', description: 'Whether the download was successful' },
|
success: { type: 'boolean', description: 'Whether the download was successful' },
|
||||||
|
file: { type: 'file', description: 'Downloaded file stored in execution files' },
|
||||||
fileName: { type: 'string', description: 'Name of the downloaded file' },
|
fileName: { type: 'string', description: 'Name of the downloaded file' },
|
||||||
content: { type: 'string', description: 'File content (text or base64 encoded)' },
|
content: { type: 'string', description: 'File content (text or base64 encoded)' },
|
||||||
size: { type: 'number', description: 'File size in bytes' },
|
size: { type: 'number', description: 'File size in bytes' },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
export interface SftpConnectionConfig {
|
export interface SftpConnectionConfig {
|
||||||
host: string
|
host: string
|
||||||
@@ -42,6 +42,7 @@ export interface SftpDownloadResult extends ToolResponse {
|
|||||||
output: {
|
output: {
|
||||||
success: boolean
|
success: boolean
|
||||||
fileName?: string
|
fileName?: string
|
||||||
|
file?: ToolFileData
|
||||||
content?: string
|
content?: string
|
||||||
size?: number
|
size?: number
|
||||||
encoding?: string
|
encoding?: string
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import type {
|
import type {
|
||||||
SharepointGetListResponse,
|
SharepointGetListResponse,
|
||||||
SharepointList,
|
SharepointList,
|
||||||
@@ -56,7 +57,10 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
|
|||||||
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists`
|
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists`
|
||||||
const url = new URL(baseUrl)
|
const url = new URL(baseUrl)
|
||||||
const finalUrl = url.toString()
|
const finalUrl = url.toString()
|
||||||
logger.info('SharePoint List All Lists URL', { finalUrl, siteId })
|
logger.info('SharePoint List All Lists URL', {
|
||||||
|
finalUrl: sanitizeUrlForLog(finalUrl),
|
||||||
|
siteId,
|
||||||
|
})
|
||||||
return finalUrl
|
return finalUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
|
|||||||
itemsUrl.searchParams.set('$expand', 'fields')
|
itemsUrl.searchParams.set('$expand', 'fields')
|
||||||
const finalItemsUrl = itemsUrl.toString()
|
const finalItemsUrl = itemsUrl.toString()
|
||||||
logger.info('SharePoint Get List Items URL', {
|
logger.info('SharePoint Get List Items URL', {
|
||||||
finalUrl: finalItemsUrl,
|
finalUrl: sanitizeUrlForLog(finalItemsUrl),
|
||||||
siteId,
|
siteId,
|
||||||
listId: params.listId,
|
listId: params.listId,
|
||||||
})
|
})
|
||||||
@@ -89,7 +93,7 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
|
|||||||
|
|
||||||
const finalUrl = url.toString()
|
const finalUrl = url.toString()
|
||||||
logger.info('SharePoint Get List URL', {
|
logger.info('SharePoint Get List URL', {
|
||||||
finalUrl,
|
finalUrl: sanitizeUrlForLog(finalUrl),
|
||||||
siteId,
|
siteId,
|
||||||
listId: params.listId,
|
listId: params.listId,
|
||||||
includeColumns: !!params.includeColumns,
|
includeColumns: !!params.includeColumns,
|
||||||
|
|||||||
@@ -114,5 +114,6 @@ export const slackMessageTool: ToolConfig<SlackMessageParams, SlackMessageRespon
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'Number of files uploaded (when files are attached)',
|
description: 'Number of files uploaded (when files are attached)',
|
||||||
},
|
},
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
import type { OutputProperty, ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared output property definitions for Slack API responses.
|
* Shared output property definitions for Slack API responses.
|
||||||
@@ -596,6 +596,7 @@ export interface SlackMessageResponse extends ToolResponse {
|
|||||||
ts: string
|
ts: string
|
||||||
channel: string
|
channel: string
|
||||||
fileCount?: number
|
fileCount?: number
|
||||||
|
files?: ToolFileData[]
|
||||||
// New comprehensive message object
|
// New comprehensive message object
|
||||||
message: SlackMessage
|
message: SlackMessage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const downloadFileTool: ToolConfig<SSHDownloadFileParams, SSHResponse> =
|
|||||||
success: true,
|
success: true,
|
||||||
output: {
|
output: {
|
||||||
downloaded: true,
|
downloaded: true,
|
||||||
|
file: data.file,
|
||||||
fileContent: data.content,
|
fileContent: data.content,
|
||||||
fileName: data.fileName,
|
fileName: data.fileName,
|
||||||
remotePath: data.remotePath,
|
remotePath: data.remotePath,
|
||||||
@@ -91,6 +92,7 @@ export const downloadFileTool: ToolConfig<SSHDownloadFileParams, SSHResponse> =
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
downloaded: { type: 'boolean', description: 'Whether the file was downloaded successfully' },
|
downloaded: { type: 'boolean', description: 'Whether the file was downloaded successfully' },
|
||||||
|
file: { type: 'file', description: 'Downloaded file stored in execution files' },
|
||||||
fileContent: { type: 'string', description: 'File content (base64 encoded for binary files)' },
|
fileContent: { type: 'string', description: 'File content (base64 encoded for binary files)' },
|
||||||
fileName: { type: 'string', description: 'Name of the downloaded file' },
|
fileName: { type: 'string', description: 'Name of the downloaded file' },
|
||||||
remotePath: { type: 'string', description: 'Source path on the remote server' },
|
remotePath: { type: 'string', description: 'Source path on the remote server' },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
// Base SSH connection configuration
|
// Base SSH connection configuration
|
||||||
export interface SSHConnectionConfig {
|
export interface SSHConnectionConfig {
|
||||||
@@ -149,6 +149,7 @@ export interface SSHResponse extends ToolResponse {
|
|||||||
|
|
||||||
uploaded?: boolean
|
uploaded?: boolean
|
||||||
downloaded?: boolean
|
downloaded?: boolean
|
||||||
|
file?: ToolFileData
|
||||||
fileContent?: string
|
fileContent?: string
|
||||||
fileName?: string
|
fileName?: string
|
||||||
remotePath?: string
|
remotePath?: string
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
|
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
|
||||||
import type { StagehandAgentParams, StagehandAgentResponse } from '@/tools/stagehand/types'
|
import type { StagehandAgentParams, StagehandAgentResponse } from '@/tools/stagehand/types'
|
||||||
import { STAGEHAND_AGENT_RESULT_OUTPUT_PROPERTIES } from '@/tools/stagehand/types'
|
import { STAGEHAND_AGENT_RESULT_OUTPUT_PROPERTIES } from '@/tools/stagehand/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
@@ -61,7 +62,9 @@ export const agentTool: ToolConfig<StagehandAgentParams, StagehandAgentResponse>
|
|||||||
let startUrl = params.startUrl
|
let startUrl = params.startUrl
|
||||||
if (startUrl && !startUrl.match(/^https?:\/\//i)) {
|
if (startUrl && !startUrl.match(/^https?:\/\//i)) {
|
||||||
startUrl = `https://${startUrl.trim()}`
|
startUrl = `https://${startUrl.trim()}`
|
||||||
logger.info(`Normalized URL from ${params.startUrl} to ${startUrl}`)
|
logger.info(
|
||||||
|
`Normalized URL from ${sanitizeUrlForLog(params.startUrl)} to ${sanitizeUrlForLog(startUrl)}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -441,7 +442,7 @@ export interface SupabaseStorageUploadParams {
|
|||||||
bucket: string
|
bucket: string
|
||||||
fileName: string
|
fileName: string
|
||||||
path?: string
|
path?: string
|
||||||
fileData: any // UserFile object (basic mode) or string (advanced mode: base64/plain text)
|
fileData: UserFile | string
|
||||||
contentType?: string
|
contentType?: string
|
||||||
upsert?: boolean
|
upsert?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export const telegramSendDocumentTool: ToolConfig<
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
message: { type: 'string', description: 'Success or error message' },
|
message: { type: 'string', description: 'Success or error message' },
|
||||||
|
files: { type: 'file[]', description: 'Files attached to the message' },
|
||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: 'Telegram message data including document',
|
description: 'Telegram message data including document',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { UserFile } from '@/executor/types'
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
export interface TelegramMessage {
|
export interface TelegramMessage {
|
||||||
message_id: number
|
message_id: number
|
||||||
@@ -167,6 +167,7 @@ export interface TelegramSendDocumentResponse extends ToolResponse {
|
|||||||
output: {
|
output: {
|
||||||
message: string
|
message: string
|
||||||
data?: TelegramMedia
|
data?: TelegramMedia
|
||||||
|
files?: ToolFileData[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Common types for WordPress REST API tools
|
// Common types for WordPress REST API tools
|
||||||
|
import type { UserFile } from '@/executor/types'
|
||||||
import type { ToolResponse } from '@/tools/types'
|
import type { ToolResponse } from '@/tools/types'
|
||||||
|
|
||||||
// Common parameters for all WordPress tools (WordPress.com OAuth)
|
// Common parameters for all WordPress tools (WordPress.com OAuth)
|
||||||
@@ -254,7 +255,7 @@ export interface WordPressListPagesResponse extends ToolResponse {
|
|||||||
|
|
||||||
// Upload Media
|
// Upload Media
|
||||||
export interface WordPressUploadMediaParams extends WordPressBaseParams {
|
export interface WordPressUploadMediaParams extends WordPressBaseParams {
|
||||||
file: any // UserFile object from file upload
|
file: UserFile
|
||||||
filename?: string // Optional filename override
|
filename?: string // Optional filename override
|
||||||
title?: string
|
title?: string
|
||||||
caption?: string
|
caption?: string
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const microsoftTeamsWebhookTrigger: TriggerConfig = {
|
|||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
raw: {
|
raw: {
|
||||||
attachments: { type: 'file[]', description: 'Array of attachments' },
|
attachments: { type: 'json', description: 'Array of attachments' },
|
||||||
channelData: {
|
channelData: {
|
||||||
team: { id: { type: 'string', description: 'Team ID' } },
|
team: { id: { type: 'string', description: 'Team ID' } },
|
||||||
tenant: { id: { type: 'string', description: 'Tenant ID' } },
|
tenant: { id: { type: 'string', description: 'Tenant ID' } },
|
||||||
|
|||||||
Reference in New Issue
Block a user