Skip to content

Commit 5a0becf

Browse files
committed
fix integrations
1 parent f4a3c94 commit 5a0becf

File tree

13 files changed

+187
-36
lines changed

13 files changed

+187
-36
lines changed

apps/sim/app/api/tools/confluence/upload-attachment/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export async function POST(request: NextRequest) {
9292
formData.append('comment', comment)
9393
}
9494

95+
// Add minorEdit field as required by Confluence API
96+
formData.append('minorEdit', 'false')
97+
9598
const response = await fetch(url, {
9699
method: 'POST',
97100
headers: {

apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ export async function POST(request: NextRequest) {
9494

9595
for (const file of userFiles) {
9696
try {
97+
// Microsoft Graph API limits direct uploads to 4MB
98+
const maxSize = 4 * 1024 * 1024
99+
if (file.size > maxSize) {
100+
const sizeMB = (file.size / (1024 * 1024)).toFixed(2)
101+
logger.error(
102+
`[${requestId}] File ${file.name} is ${sizeMB}MB, exceeds 4MB limit for direct upload`
103+
)
104+
throw new Error(
105+
`File "${file.name}" (${sizeMB}MB) exceeds the 4MB limit for Teams attachments. Use smaller files or upload to SharePoint/OneDrive first.`
106+
)
107+
}
108+
97109
logger.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
98110

99111
const buffer = await downloadFileFromStorage(file, requestId, logger)

apps/sim/app/api/tools/mistral/parse/route.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,19 @@ export async function POST(request: NextRequest) {
101101
const base64Payload = base64.startsWith('data:')
102102
? base64
103103
: `data:${mimeType};base64,${base64}`
104-
mistralBody.document = {
105-
type: 'document_url',
106-
document_url: base64Payload,
104+
105+
// Mistral API uses different document types for images vs documents
106+
const isImage = mimeType.startsWith('image/')
107+
if (isImage) {
108+
mistralBody.document = {
109+
type: 'image_url',
110+
image_url: base64Payload,
111+
}
112+
} else {
113+
mistralBody.document = {
114+
type: 'document_url',
115+
document_url: base64Payload,
116+
}
107117
}
108118
} else if (filePath) {
109119
let fileUrl = filePath
@@ -146,9 +156,26 @@ export async function POST(request: NextRequest) {
146156
}
147157
}
148158

149-
mistralBody.document = {
150-
type: 'document_url',
151-
document_url: fileUrl,
159+
// Detect image URLs by extension for proper Mistral API type
160+
const lowerUrl = fileUrl.toLowerCase()
161+
const isImageUrl =
162+
lowerUrl.endsWith('.png') ||
163+
lowerUrl.endsWith('.jpg') ||
164+
lowerUrl.endsWith('.jpeg') ||
165+
lowerUrl.endsWith('.gif') ||
166+
lowerUrl.endsWith('.webp') ||
167+
lowerUrl.endsWith('.avif')
168+
169+
if (isImageUrl) {
170+
mistralBody.document = {
171+
type: 'image_url',
172+
image_url: fileUrl,
173+
}
174+
} else {
175+
mistralBody.document = {
176+
type: 'document_url',
177+
document_url: fileUrl,
178+
}
152179
}
153180
}
154181

apps/sim/app/api/tools/onedrive/upload/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const OneDriveUploadSchema = z.object({
3838
folderId: z.string().optional().nullable(),
3939
mimeType: z.string().nullish(),
4040
values: ExcelValuesSchema.optional().nullable(),
41+
conflictBehavior: z.enum(['fail', 'replace', 'rename']).optional().nullable(),
4142
})
4243

4344
async function secureFetchGraph(
@@ -184,6 +185,11 @@ export async function POST(request: NextRequest) {
184185
uploadUrl = `${MICROSOFT_GRAPH_BASE}/me/drive/root:/${encodeURIComponent(fileName)}:/content`
185186
}
186187

188+
// Add conflict behavior if specified (defaults to replace by Microsoft Graph API)
189+
if (validatedData.conflictBehavior) {
190+
uploadUrl += `?@microsoft.graph.conflictBehavior=${validatedData.conflictBehavior}`
191+
}
192+
187193
const uploadResponse = await secureFetchGraph(
188194
uploadUrl,
189195
{

apps/sim/app/api/tools/outlook/send/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,14 @@ export async function POST(request: NextRequest) {
9696

9797
if (attachments.length > 0) {
9898
const totalSize = attachments.reduce((sum, file) => sum + file.size, 0)
99-
const maxSize = 4 * 1024 * 1024 // 4MB
99+
const maxSize = 3 * 1024 * 1024 // 3MB - Microsoft Graph API limit for inline attachments
100100

101101
if (totalSize > maxSize) {
102102
const sizeMB = (totalSize / (1024 * 1024)).toFixed(2)
103103
return NextResponse.json(
104104
{
105105
success: false,
106-
error: `Total attachment size (${sizeMB}MB) exceeds Outlook's limit of 4MB per request`,
106+
error: `Total attachment size (${sizeMB}MB) exceeds Microsoft Graph API limit of 3MB per request`,
107107
},
108108
{ status: 400 }
109109
)

apps/sim/app/api/tools/reducto/parse/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,13 @@ export async function POST(request: NextRequest) {
175175
}
176176

177177
if (validatedData.pages && validatedData.pages.length > 0) {
178+
// Reducto API expects page_range as an object with start/end, not an array
179+
const pages = validatedData.pages
178180
reductoBody.settings = {
179-
page_range: validatedData.pages,
181+
page_range: {
182+
start: Math.min(...pages),
183+
end: Math.max(...pages),
184+
},
180185
}
181186
}
182187

apps/sim/app/api/tools/sharepoint/upload/route.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ export async function POST(request: NextRequest) {
114114
)
115115

116116
if (!driveResponse.ok) {
117-
const errorData = await driveResponse.json().catch(() => ({}))
117+
const errorData = (await driveResponse.json().catch(() => ({}))) as {
118+
error?: { message?: string }
119+
}
118120
logger.error(`[${requestId}] Failed to get default drive:`, errorData)
119121
return NextResponse.json(
120122
{
@@ -125,7 +127,7 @@ export async function POST(request: NextRequest) {
125127
)
126128
}
127129

128-
const driveData = await driveResponse.json()
130+
const driveData = (await driveResponse.json()) as { id: string }
129131
effectiveDriveId = driveData.id
130132
logger.info(`[${requestId}] Using default drive: ${effectiveDriveId}`)
131133
}
@@ -187,20 +189,76 @@ export async function POST(request: NextRequest) {
187189
logger.error(`[${requestId}] Failed to upload file ${fileName}:`, errorData)
188190

189191
if (uploadResponse.status === 409) {
190-
logger.warn(`[${requestId}] File ${fileName} already exists, attempting to replace`)
192+
// File exists - retry with conflict behavior set to replace
193+
logger.warn(`[${requestId}] File ${fileName} already exists, retrying with replace`)
194+
const replaceUrl = `${uploadUrl}?@microsoft.graph.conflictBehavior=replace`
195+
const replaceResponse = await secureFetchGraph(
196+
replaceUrl,
197+
{
198+
method: 'PUT',
199+
headers: {
200+
Authorization: `Bearer ${validatedData.accessToken}`,
201+
'Content-Type': userFile.type || 'application/octet-stream',
202+
},
203+
body: buffer,
204+
},
205+
'replaceUrl'
206+
)
207+
208+
if (!replaceResponse.ok) {
209+
const replaceErrorData = (await replaceResponse.json().catch(() => ({}))) as {
210+
error?: { message?: string }
211+
}
212+
logger.error(`[${requestId}] Failed to replace file ${fileName}:`, replaceErrorData)
213+
return NextResponse.json(
214+
{
215+
success: false,
216+
error: replaceErrorData.error?.message || `Failed to replace file: ${fileName}`,
217+
},
218+
{ status: replaceResponse.status }
219+
)
220+
}
221+
222+
const replaceData = (await replaceResponse.json()) as {
223+
id: string
224+
name: string
225+
webUrl: string
226+
size: number
227+
createdDateTime: string
228+
lastModifiedDateTime: string
229+
}
230+
logger.info(`[${requestId}] File replaced successfully: ${fileName}`)
231+
232+
uploadedFiles.push({
233+
id: replaceData.id,
234+
name: replaceData.name,
235+
webUrl: replaceData.webUrl,
236+
size: replaceData.size,
237+
createdDateTime: replaceData.createdDateTime,
238+
lastModifiedDateTime: replaceData.lastModifiedDateTime,
239+
})
191240
continue
192241
}
193242

194243
return NextResponse.json(
195244
{
196245
success: false,
197-
error: errorData.error?.message || `Failed to upload file: ${fileName}`,
246+
error:
247+
(errorData as { error?: { message?: string } }).error?.message ||
248+
`Failed to upload file: ${fileName}`,
198249
},
199250
{ status: uploadResponse.status }
200251
)
201252
}
202253

203-
const uploadData = await uploadResponse.json()
254+
const uploadData = (await uploadResponse.json()) as {
255+
id: string
256+
name: string
257+
webUrl: string
258+
size: number
259+
createdDateTime: string
260+
lastModifiedDateTime: string
261+
}
204262
logger.info(`[${requestId}] File uploaded successfully: ${fileName}`)
205263

206264
uploadedFiles.push({

apps/sim/app/api/tools/slack/utils.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ export async function completeSlackFileUpload(
156156
uploadedFileIds: string[],
157157
channel: string,
158158
text: string,
159-
accessToken: string
159+
accessToken: string,
160+
threadTs?: string | null
160161
): Promise<{ ok: boolean; files?: any[]; error?: string }> {
161162
const response = await fetch('https://slack.com/api/files.completeUploadExternal', {
162163
method: 'POST',
@@ -168,6 +169,7 @@ export async function completeSlackFileUpload(
168169
files: uploadedFileIds.map((id) => ({ id })),
169170
channel_id: channel,
170171
initial_comment: text,
172+
...(threadTs && { thread_ts: threadTs }),
171173
}),
172174
})
173175

@@ -307,8 +309,8 @@ export async function sendSlackMessage(
307309
return { success: true, output: formatMessageSuccessResponse(data, text) }
308310
}
309311

310-
// Complete file upload
311-
const completeData = await completeSlackFileUpload(fileIds, channel, text, accessToken)
312+
// Complete file upload with thread support
313+
const completeData = await completeSlackFileUpload(fileIds, channel, text, accessToken, threadTs)
312314

313315
if (!completeData.ok) {
314316
logger.error(`[${requestId}] Failed to complete upload:`, completeData.error)

apps/sim/app/api/tools/ssh/download-file/route.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ export async function POST(request: NextRequest) {
8080
})
8181
})
8282

83+
// Check file size limit (50MB to prevent memory exhaustion)
84+
const maxSize = 50 * 1024 * 1024
85+
if (stats.size > maxSize) {
86+
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2)
87+
return NextResponse.json(
88+
{ error: `File size (${sizeMB}MB) exceeds download limit of 50MB` },
89+
{ status: 400 }
90+
)
91+
}
92+
8393
// Read file content
8494
const content = await new Promise<Buffer>((resolve, reject) => {
8595
const chunks: Buffer[] = []

apps/sim/app/api/tools/stt/route.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ export async function POST(request: NextRequest) {
201201
translateToEnglish,
202202
model,
203203
body.prompt,
204-
body.temperature
204+
body.temperature,
205+
audioMimeType,
206+
audioFileName
205207
)
206208
transcript = result.transcript
207209
segments = result.segments
@@ -214,7 +216,8 @@ export async function POST(request: NextRequest) {
214216
language,
215217
timestamps,
216218
diarization,
217-
model
219+
model,
220+
audioMimeType
218221
)
219222
transcript = result.transcript
220223
segments = result.segments
@@ -304,7 +307,9 @@ async function transcribeWithWhisper(
304307
translate?: boolean,
305308
model?: string,
306309
prompt?: string,
307-
temperature?: number
310+
temperature?: number,
311+
mimeType?: string,
312+
fileName?: string
308313
): Promise<{
309314
transcript: string
310315
segments?: TranscriptSegment[]
@@ -313,8 +318,11 @@ async function transcribeWithWhisper(
313318
}> {
314319
const formData = new FormData()
315320

316-
const blob = new Blob([new Uint8Array(audioBuffer)], { type: 'audio/mpeg' })
317-
formData.append('file', blob, 'audio.mp3')
321+
// Use actual MIME type and filename if provided
322+
const actualMimeType = mimeType || 'audio/mpeg'
323+
const actualFileName = fileName || 'audio.mp3'
324+
const blob = new Blob([new Uint8Array(audioBuffer)], { type: actualMimeType })
325+
formData.append('file', blob, actualFileName)
318326
formData.append('model', model || 'whisper-1')
319327

320328
if (language && language !== 'auto') {
@@ -331,10 +339,11 @@ async function transcribeWithWhisper(
331339

332340
formData.append('response_format', 'verbose_json')
333341

342+
// OpenAI API uses array notation for timestamp_granularities
334343
if (timestamps === 'word') {
335-
formData.append('timestamp_granularities', 'word')
344+
formData.append('timestamp_granularities[]', 'word')
336345
} else if (timestamps === 'sentence') {
337-
formData.append('timestamp_granularities', 'segment')
346+
formData.append('timestamp_granularities[]', 'segment')
338347
}
339348

340349
const endpoint = translate ? 'translations' : 'transcriptions'
@@ -377,7 +386,8 @@ async function transcribeWithDeepgram(
377386
language?: string,
378387
timestamps?: 'none' | 'sentence' | 'word',
379388
diarization?: boolean,
380-
model?: string
389+
model?: string,
390+
mimeType?: string
381391
): Promise<{
382392
transcript: string
383393
segments?: TranscriptSegment[]
@@ -409,7 +419,7 @@ async function transcribeWithDeepgram(
409419
method: 'POST',
410420
headers: {
411421
Authorization: `Token ${apiKey}`,
412-
'Content-Type': 'audio/mpeg',
422+
'Content-Type': mimeType || 'audio/mpeg',
413423
},
414424
body: new Uint8Array(audioBuffer),
415425
})
@@ -565,7 +575,8 @@ async function transcribeWithAssemblyAI(
565575
audio_url: upload_url,
566576
}
567577

568-
if (model === 'best' || model === 'nano') {
578+
// AssemblyAI only supports 'best', 'slam-1', or 'universal' for speech_model
579+
if (model === 'best') {
569580
transcriptRequest.speech_model = model
570581
}
571582

0 commit comments

Comments
 (0)