fix(integrations): gdrive trashed search, slack blocks-with-file, slack get_message ts (#4600)

* fix(integrations): gdrive trashed search, slack blocks-with-file, slack get_message ts

- Google Drive search/list: skip default `trashed = false` when user query
  already specifies a `trashed = ...` predicate, so trashed-file searches work.
- Slack send-message with files: forward `blocks` through to
  `files.completeUploadExternal` so Block Kit renders when files are attached.
- Slack get_message: switch from `conversations.history` (oldest lower-bound
  returned the next message after) to `conversations.replies` with `ts=`
  for exact-match lookup, plus a defensive ts-equality guard and clearer error.

* fix(google_drive): revert list.ts trashed guard — query is plain text, not gdrive syntax

* fix(slack): omit initial_comment when blocks present so Block Kit actually renders on file uploads
This commit is contained in:
Waleed
2026-05-14 11:33:16 -07:00
committed by GitHub
parent 642231f875
commit 044e034719
4 changed files with 45 additions and 13 deletions
+14 -3
View File
@@ -141,7 +141,8 @@ async function completeSlackFileUpload(
channel: string,
text: string,
accessToken: string,
threadTs?: string | null
threadTs?: string | null,
blocks?: unknown[] | null
): Promise<{ ok: boolean; files?: any[]; error?: string }> {
const response = await fetch('https://slack.com/api/files.completeUploadExternal', {
method: 'POST',
@@ -152,7 +153,10 @@ async function completeSlackFileUpload(
body: JSON.stringify({
files: uploadedFileIds.map((id) => ({ id })),
channel_id: channel,
initial_comment: text,
// Per Slack docs for files.completeUploadExternal: if `initial_comment`
// is provided, `blocks` is silently ignored. So when blocks are present
// we omit initial_comment and let blocks render instead.
...(blocks && blocks.length > 0 ? { blocks } : { initial_comment: text }),
...(threadTs && { thread_ts: threadTs }),
}),
})
@@ -295,7 +299,14 @@ export async function sendSlackMessage(
}
// Complete file upload with thread support
const completeData = await completeSlackFileUpload(fileIds, channel, text, accessToken, threadTs)
const completeData = await completeSlackFileUpload(
fileIds,
channel,
text,
accessToken,
threadTs,
blocks
)
if (!completeData.ok) {
logger.error(`[${requestId}] Failed to complete upload:`, completeData.error)
+5 -2
View File
@@ -66,8 +66,11 @@ export const listTool: ToolConfig<GoogleDriveToolParams, GoogleDriveListResponse
const escapeQueryValue = (value: string): string =>
value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
// Build the query conditions
const conditions = ['trashed = false'] // Always exclude trashed files
// Build the query conditions. `params.query` here is a plain-text name
// search term (wrapped in `name contains '...'` below), not Google Drive
// query syntax — so there's no caller-supplied `trashed` predicate to
// honour. Always exclude trashed files.
const conditions: string[] = ['trashed = false']
const folderId = (params.folderId || params.folderSelector)?.trim()
if (folderId) {
const escapedFolderId = escapeQueryValue(folderId)
+8 -3
View File
@@ -64,9 +64,14 @@ export const searchTool: ToolConfig<GoogleDriveSearchParams, GoogleDriveSearchRe
url.searchParams.append('includeItemsFromAllDrives', 'true')
// The query is passed directly as Google Drive query syntax
const conditions = ['trashed = false']
if (params.query?.trim()) {
conditions.push(params.query.trim())
const userQuery = params.query?.trim()
const userSpecifiesTrashed = userQuery ? /\btrashed\s*=/.test(userQuery) : false
const conditions: string[] = []
if (!userSpecifiesTrashed) {
conditions.push('trashed = false')
}
if (userQuery) {
conditions.push(userQuery)
}
url.searchParams.append('q', conditions.join(' and '))
+18 -5
View File
@@ -1,7 +1,10 @@
import { createLogger } from '@sim/logger'
import type { SlackGetMessageParams, SlackGetMessageResponse } from '@/tools/slack/types'
import { MESSAGE_OUTPUT_PROPERTIES } from '@/tools/slack/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('SlackGetMessageTool')
export const slackGetMessageTool: ToolConfig<SlackGetMessageParams, SlackGetMessageResponse> = {
id: 'slack_get_message',
name: 'Slack Get Message',
@@ -49,11 +52,10 @@ export const slackGetMessageTool: ToolConfig<SlackGetMessageParams, SlackGetMess
request: {
url: (params: SlackGetMessageParams) => {
const url = new URL('https://slack.com/api/conversations.history')
const url = new URL('https://slack.com/api/conversations.replies')
url.searchParams.append('channel', params.channel?.trim() ?? '')
url.searchParams.append('oldest', params.timestamp?.trim() ?? '')
url.searchParams.append('ts', params.timestamp?.trim() ?? '')
url.searchParams.append('limit', '1')
url.searchParams.append('inclusive', 'true')
return url.toString()
},
method: 'GET',
@@ -63,8 +65,9 @@ export const slackGetMessageTool: ToolConfig<SlackGetMessageParams, SlackGetMess
}),
},
transformResponse: async (response: Response) => {
transformResponse: async (response: Response, params?: SlackGetMessageParams) => {
const data = await response.json()
const requestedTs = params?.timestamp?.trim() ?? ''
if (!data.ok) {
if (data.error === 'missing_scope') {
@@ -78,15 +81,25 @@ export const slackGetMessageTool: ToolConfig<SlackGetMessageParams, SlackGetMess
if (data.error === 'channel_not_found') {
throw new Error('Channel not found. Please check the channel ID.')
}
if (data.error === 'message_not_found' || data.error === 'thread_not_found') {
throw new Error(`Message not found at timestamp ${requestedTs}`)
}
throw new Error(data.error || 'Failed to get message from Slack')
}
const messages = data.messages || []
if (messages.length === 0) {
throw new Error('Message not found')
throw new Error(`Message not found at timestamp ${requestedTs}`)
}
const msg = messages[0]
if (requestedTs && msg.ts !== requestedTs) {
logger.warn('Slack returned a message with a different timestamp than requested', {
requestedTs,
returnedTs: msg.ts,
})
throw new Error(`Message not found at timestamp ${requestedTs}`)
}
const message = {
type: msg.type ?? 'message',
ts: msg.ts,