From 79f81811c38aad3e9928926f38d610ebaf568d05 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 15:11:50 -0800 Subject: [PATCH 01/11] new error throw and improvement --- apps/sim/app/api/auth/oauth/token/route.ts | 17 +++- apps/sim/app/api/auth/oauth/utils.ts | 37 +++++--- apps/sim/blocks/blocks/google_vault.ts | 91 +++++++++++++++++++ apps/sim/lib/oauth/oauth.ts | 29 +++++- .../google_vault/create_matters_export.ts | 19 +++- .../google_vault/create_matters_holds.ts | 45 +++++++++ apps/sim/tools/google_vault/types.ts | 7 +- 7 files changed, 222 insertions(+), 23 deletions(-) diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index f5c8d7b617..6e65cefb9f 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -129,8 +129,12 @@ export async function POST(request: NextRequest) { { status: 200 } ) } catch (error) { - logger.error(`[${requestId}] Failed to refresh access token:`, error) - return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 }) + const errorMessage = error instanceof Error ? error.message : 'Failed to refresh access token' + logger.error(`[${requestId}] Failed to refresh access token:`, { + error: errorMessage, + stack: error instanceof Error ? error.stack : undefined, + }) + return NextResponse.json({ error: errorMessage }, { status: 401 }) } } catch (error) { logger.error(`[${requestId}] Error getting access token`, error) @@ -207,8 +211,13 @@ export async function GET(request: NextRequest) { }, { status: 200 } ) - } catch (_error) { - return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Failed to refresh access token' + logger.error(`[${requestId}] Failed to refresh access token:`, { + error: errorMessage, + stack: error instanceof Error ? error.stack : undefined, + }) + return NextResponse.json({ error: errorMessage }, { status: 401 }) } } catch (error) { logger.error(`[${requestId}] Error fetching access token`, error) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 08dd16fdff..6ed65298b7 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -135,11 +135,14 @@ export async function getOAuthToken(userId: string, providerId: string): Promise const refreshResult = await refreshOAuthToken(providerId, credential.refreshToken!) if (!refreshResult) { - logger.error(`Failed to refresh token for user ${userId}, provider ${providerId}`, { - providerId, - userId, - hasRefreshToken: !!credential.refreshToken, - }) + logger.error( + `Failed to refresh token for user ${userId}, provider ${providerId} - no result returned`, + { + providerId, + userId, + hasRefreshToken: !!credential.refreshToken, + } + ) return null } @@ -170,7 +173,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise providerId, userId, }) - return null + throw error } } @@ -221,12 +224,15 @@ export async function refreshAccessTokenIfNeeded( ) if (!refreshedToken) { - logger.error(`[${requestId}] Failed to refresh token for credential: ${credentialId}`, { - credentialId, - providerId: credential.providerId, - userId: credential.userId, - hasRefreshToken: !!credential.refreshToken, - }) + logger.error( + `[${requestId}] Failed to refresh token for credential: ${credentialId} - no result returned`, + { + credentialId, + providerId: credential.providerId, + userId: credential.userId, + hasRefreshToken: !!credential.refreshToken, + } + ) return null } @@ -249,6 +255,7 @@ export async function refreshAccessTokenIfNeeded( logger.info(`[${requestId}] Successfully refreshed access token for credential`) return refreshedToken.accessToken } catch (error) { + // Re-throw the error to propagate detailed error messages (e.g., session expiry instructions) logger.error(`[${requestId}] Error refreshing token for credential`, { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, @@ -256,7 +263,7 @@ export async function refreshAccessTokenIfNeeded( credentialId, userId: credential.userId, }) - return null + throw error } } else if (!accessToken) { // We have no access token and either no refresh token or not eligible to refresh @@ -292,8 +299,8 @@ export async function refreshTokenIfNeeded( const refreshResult = await refreshOAuthToken(credential.providerId, credential.refreshToken!) if (!refreshResult) { - logger.error(`[${requestId}] Failed to refresh token for credential`) - throw new Error('Failed to refresh token') + logger.error(`[${requestId}] Failed to refresh token for credential - no result returned`) + throw new Error('Failed to refresh token: no result returned from provider') } const { accessToken: refreshedToken, expiresIn, refreshToken: newRefreshToken } = refreshResult diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index c54098aad3..3de897bb16 100644 --- a/apps/sim/blocks/blocks/google_vault.ts +++ b/apps/sim/blocks/blocks/google_vault.ts @@ -159,6 +159,90 @@ Return ONLY the hold name - no explanations, no quotes, no extra text.`, placeholder: 'Org Unit ID (alternative to emails)', condition: { field: 'operation', value: ['create_matters_holds', 'create_matters_export'] }, }, + // Date filtering for exports and holds (holds only support MAIL and GROUPS corpus) + { + id: 'startTime', + title: 'Start Time', + type: 'short-input', + placeholder: 'YYYY-MM-DDTHH:mm:ssZ', + condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. +The timestamp should be in the format: YYYY-MM-DDTHH:mm:ssZ (UTC timezone). +Note: Google Vault rounds times to 12 AM on the specified date. +Examples: +- "yesterday" -> Calculate yesterday's date at 00:00:00Z +- "last week" -> Calculate 7 days ago at 00:00:00Z +- "beginning of this month" -> Calculate the 1st of current month at 00:00:00Z +- "January 1, 2024" -> 2024-01-01T00:00:00Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the start date (e.g., "last month", "January 1, 2024")...', + generationType: 'timestamp', + }, + }, + { + id: 'endTime', + title: 'End Time', + type: 'short-input', + placeholder: 'YYYY-MM-DDTHH:mm:ssZ', + condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. +The timestamp should be in the format: YYYY-MM-DDTHH:mm:ssZ (UTC timezone). +Note: Google Vault rounds times to 12 AM on the specified date. +Examples: +- "now" -> Current timestamp +- "today" -> Today's date at 23:59:59Z +- "end of last month" -> Last day of previous month at 23:59:59Z +- "December 31, 2024" -> 2024-12-31T23:59:59Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the end date (e.g., "today", "end of last quarter")...', + generationType: 'timestamp', + }, + }, + { + id: 'terms', + title: 'Search Terms', + type: 'long-input', + placeholder: 'Enter search query (e.g., from:user@example.com subject:confidential)', + condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + wandConfig: { + enabled: true, + prompt: `Generate a Google Vault search query based on the user's description. +The query can use Gmail-style search operators for MAIL corpus: +- from:user@example.com - emails from specific sender +- to:user@example.com - emails to specific recipient +- subject:keyword - emails with keyword in subject +- has:attachment - emails with attachments +- filename:pdf - emails with PDF attachments +- before:YYYY/MM/DD - emails before date +- after:YYYY/MM/DD - emails after date + +For DRIVE corpus, use Drive search operators: +- owner:user@example.com - files owned by user +- type:document - specific file types + +For holds, date filtering only works with MAIL and GROUPS corpus. + +Return ONLY the search query - no explanations, no quotes, no extra text.`, + placeholder: 'Describe what content to search for...', + }, + }, + // Drive-specific option for holds + { + id: 'includeSharedDrives', + title: 'Include Shared Drives', + type: 'switch', + condition: { + field: 'operation', + value: 'create_matters_holds', + and: { field: 'corpus', value: 'DRIVE' }, + }, + }, { id: 'exportId', title: 'Export ID', @@ -296,9 +380,16 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, corpus: { type: 'string', description: 'Data corpus (MAIL, DRIVE, GROUPS, etc.)' }, accountEmails: { type: 'string', description: 'Comma-separated account emails' }, orgUnitId: { type: 'string', description: 'Organization unit ID' }, + startTime: { type: 'string', description: 'Start time for date filtering (ISO 8601 format)' }, + endTime: { type: 'string', description: 'End time for date filtering (ISO 8601 format)' }, + terms: { type: 'string', description: 'Search query terms' }, // Create hold inputs holdName: { type: 'string', description: 'Name for the hold' }, + includeSharedDrives: { + type: 'boolean', + description: 'Include files in shared drives (for DRIVE corpus holds)', + }, // Download export file inputs bucketName: { type: 'string', description: 'GCS bucket name from export' }, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index d00fea2e4e..99f65ee400 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -1171,7 +1171,7 @@ export async function refreshOAuthToken( if (!response.ok) { const errorText = await response.text() - let errorData = errorText + let errorData: any = errorText try { errorData = JSON.parse(errorText) @@ -1191,6 +1191,29 @@ export async function refreshOAuthToken( hasRefreshToken: !!refreshToken, refreshTokenPrefix: refreshToken ? `${refreshToken.substring(0, 10)}...` : 'none', }) + + // Check for Google Workspace session control errors (RAPT - Reauthentication Policy Token) + // This occurs when the organization enforces periodic re-authentication + if ( + typeof errorData === 'object' && + (errorData.error_subtype === 'invalid_rapt' || + errorData.error_description?.includes('reauth related error')) + ) { + throw new Error( + `Session expired due to organization security policy. Please reconnect your ${providerId} account to continue. Alternatively, ask your Google Workspace admin to exempt this app from session control: Admin Console → Security → Google Cloud session control → "Exempt trusted apps".` + ) + } + + if ( + typeof errorData === 'object' && + errorData.error === 'invalid_grant' && + !errorData.error_subtype + ) { + throw new Error( + `Access has been revoked or the refresh token is no longer valid. Please reconnect your ${providerId} account.` + ) + } + throw new Error(`Failed to refresh token: ${response.status} ${errorText}`) } @@ -1224,6 +1247,8 @@ export async function refreshOAuthToken( } } catch (error) { logger.error('Error refreshing token:', { error }) - return null + // Re-throw specific errors so they propagate with their detailed messages + // Only return null for truly unexpected errors without useful messages + throw error } } diff --git a/apps/sim/tools/google_vault/create_matters_export.ts b/apps/sim/tools/google_vault/create_matters_export.ts index f468fc7ab7..3f443ce6d6 100644 --- a/apps/sim/tools/google_vault/create_matters_export.ts +++ b/apps/sim/tools/google_vault/create_matters_export.ts @@ -36,6 +36,24 @@ export const createMattersExportTool: ToolConfig Date: Thu, 8 Jan 2026 15:50:25 -0800 Subject: [PATCH 02/11] fixed critical issues --- apps/sim/app/api/auth/oauth/utils.ts | 9 ++- apps/sim/blocks/blocks/google_vault.ts | 87 ++++++++++++++++++++++++-- apps/sim/lib/oauth/oauth.ts | 2 - 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 6ed65298b7..003c8b46ef 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -173,7 +173,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise providerId, userId, }) - throw error + return null } } @@ -255,7 +255,6 @@ export async function refreshAccessTokenIfNeeded( logger.info(`[${requestId}] Successfully refreshed access token for credential`) return refreshedToken.accessToken } catch (error) { - // Re-throw the error to propagate detailed error messages (e.g., session expiry instructions) logger.error(`[${requestId}] Error refreshing token for credential`, { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, @@ -263,7 +262,7 @@ export async function refreshAccessTokenIfNeeded( credentialId, userId: credential.userId, }) - throw error + return null } } else if (!accessToken) { // We have no access token and either no refresh token or not eligible to refresh @@ -299,8 +298,8 @@ export async function refreshTokenIfNeeded( const refreshResult = await refreshOAuthToken(credential.providerId, credential.refreshToken!) if (!refreshResult) { - logger.error(`[${requestId}] Failed to refresh token for credential - no result returned`) - throw new Error('Failed to refresh token: no result returned from provider') + logger.error(`[${requestId}] Failed to refresh token for credential`) + throw new Error('Failed to refresh token') } const { accessToken: refreshedToken, expiresIn, refreshToken: newRefreshToken } = refreshResult diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index 3de897bb16..ecc5f9ff4c 100644 --- a/apps/sim/blocks/blocks/google_vault.ts +++ b/apps/sim/blocks/blocks/google_vault.ts @@ -159,13 +159,62 @@ Return ONLY the hold name - no explanations, no quotes, no extra text.`, placeholder: 'Org Unit ID (alternative to emails)', condition: { field: 'operation', value: ['create_matters_holds', 'create_matters_export'] }, }, - // Date filtering for exports and holds (holds only support MAIL and GROUPS corpus) + // Date filtering for exports (works with all corpus types) { id: 'startTime', title: 'Start Time', type: 'short-input', placeholder: 'YYYY-MM-DDTHH:mm:ssZ', - condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + condition: { field: 'operation', value: 'create_matters_export' }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. +The timestamp should be in the format: YYYY-MM-DDTHH:mm:ssZ (UTC timezone). +Note: Google Vault rounds times to 12 AM on the specified date. +Examples: +- "yesterday" -> Calculate yesterday's date at 00:00:00Z +- "last week" -> Calculate 7 days ago at 00:00:00Z +- "beginning of this month" -> Calculate the 1st of current month at 00:00:00Z +- "January 1, 2024" -> 2024-01-01T00:00:00Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the start date (e.g., "last month", "January 1, 2024")...', + generationType: 'timestamp', + }, + }, + { + id: 'endTime', + title: 'End Time', + type: 'short-input', + placeholder: 'YYYY-MM-DDTHH:mm:ssZ', + condition: { field: 'operation', value: 'create_matters_export' }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. +The timestamp should be in the format: YYYY-MM-DDTHH:mm:ssZ (UTC timezone). +Note: Google Vault rounds times to 12 AM on the specified date. +Examples: +- "now" -> Current timestamp +- "today" -> Today's date at 23:59:59Z +- "end of last month" -> Last day of previous month at 23:59:59Z +- "December 31, 2024" -> 2024-12-31T23:59:59Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the end date (e.g., "today", "end of last quarter")...', + generationType: 'timestamp', + }, + }, + // Date filtering for holds (only works with MAIL and GROUPS corpus) + { + id: 'startTime', + title: 'Start Time', + type: 'short-input', + placeholder: 'YYYY-MM-DDTHH:mm:ssZ', + condition: { + field: 'operation', + value: 'create_matters_holds', + and: { field: 'corpus', value: ['MAIL', 'GROUPS'] }, + }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. @@ -187,7 +236,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, title: 'End Time', type: 'short-input', placeholder: 'YYYY-MM-DDTHH:mm:ssZ', - condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + condition: { + field: 'operation', + value: 'create_matters_holds', + and: { field: 'corpus', value: ['MAIL', 'GROUPS'] }, + }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp in GMT based on the user's description for Google Vault date filtering. @@ -204,12 +257,13 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, generationType: 'timestamp', }, }, + // Search terms for exports (works with all corpus types) { id: 'terms', title: 'Search Terms', type: 'long-input', placeholder: 'Enter search query (e.g., from:user@example.com subject:confidential)', - condition: { field: 'operation', value: ['create_matters_export', 'create_matters_holds'] }, + condition: { field: 'operation', value: 'create_matters_export' }, wandConfig: { enabled: true, prompt: `Generate a Google Vault search query based on the user's description. @@ -226,7 +280,30 @@ For DRIVE corpus, use Drive search operators: - owner:user@example.com - files owned by user - type:document - specific file types -For holds, date filtering only works with MAIL and GROUPS corpus. +Return ONLY the search query - no explanations, no quotes, no extra text.`, + placeholder: 'Describe what content to search for...', + }, + }, + // Search terms for holds (only works with MAIL and GROUPS corpus) + { + id: 'terms', + title: 'Search Terms', + type: 'long-input', + placeholder: 'Enter search query (e.g., from:user@example.com subject:confidential)', + condition: { + field: 'operation', + value: 'create_matters_holds', + and: { field: 'corpus', value: ['MAIL', 'GROUPS'] }, + }, + wandConfig: { + enabled: true, + prompt: `Generate a Google Vault search query based on the user's description. +The query can use Gmail-style search operators: +- from:user@example.com - emails from specific sender +- to:user@example.com - emails to specific recipient +- subject:keyword - emails with keyword in subject +- has:attachment - emails with attachments +- filename:pdf - emails with PDF attachments Return ONLY the search query - no explanations, no quotes, no extra text.`, placeholder: 'Describe what content to search for...', diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 99f65ee400..b671beb878 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -1247,8 +1247,6 @@ export async function refreshOAuthToken( } } catch (error) { logger.error('Error refreshing token:', { error }) - // Re-throw specific errors so they propagate with their detailed messages - // Only return null for truly unexpected errors without useful messages throw error } } From 2c972d896e0a328e233ec053e10e563d8163c5a7 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 16:02:24 -0800 Subject: [PATCH 03/11] restore error thorwing --- apps/sim/app/api/auth/oauth/token/route.ts | 17 ++++---------- apps/sim/lib/oauth/oauth.ts | 27 ++-------------------- apps/sim/tools/google_vault/types.ts | 4 +--- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index 6e65cefb9f..f5c8d7b617 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -129,12 +129,8 @@ export async function POST(request: NextRequest) { { status: 200 } ) } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Failed to refresh access token' - logger.error(`[${requestId}] Failed to refresh access token:`, { - error: errorMessage, - stack: error instanceof Error ? error.stack : undefined, - }) - return NextResponse.json({ error: errorMessage }, { status: 401 }) + logger.error(`[${requestId}] Failed to refresh access token:`, error) + return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 }) } } catch (error) { logger.error(`[${requestId}] Error getting access token`, error) @@ -211,13 +207,8 @@ export async function GET(request: NextRequest) { }, { status: 200 } ) - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Failed to refresh access token' - logger.error(`[${requestId}] Failed to refresh access token:`, { - error: errorMessage, - stack: error instanceof Error ? error.stack : undefined, - }) - return NextResponse.json({ error: errorMessage }, { status: 401 }) + } catch (_error) { + return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 }) } } catch (error) { logger.error(`[${requestId}] Error fetching access token`, error) diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index b671beb878..d00fea2e4e 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -1171,7 +1171,7 @@ export async function refreshOAuthToken( if (!response.ok) { const errorText = await response.text() - let errorData: any = errorText + let errorData = errorText try { errorData = JSON.parse(errorText) @@ -1191,29 +1191,6 @@ export async function refreshOAuthToken( hasRefreshToken: !!refreshToken, refreshTokenPrefix: refreshToken ? `${refreshToken.substring(0, 10)}...` : 'none', }) - - // Check for Google Workspace session control errors (RAPT - Reauthentication Policy Token) - // This occurs when the organization enforces periodic re-authentication - if ( - typeof errorData === 'object' && - (errorData.error_subtype === 'invalid_rapt' || - errorData.error_description?.includes('reauth related error')) - ) { - throw new Error( - `Session expired due to organization security policy. Please reconnect your ${providerId} account to continue. Alternatively, ask your Google Workspace admin to exempt this app from session control: Admin Console → Security → Google Cloud session control → "Exempt trusted apps".` - ) - } - - if ( - typeof errorData === 'object' && - errorData.error === 'invalid_grant' && - !errorData.error_subtype - ) { - throw new Error( - `Access has been revoked or the refresh token is no longer valid. Please reconnect your ${providerId} account.` - ) - } - throw new Error(`Failed to refresh token: ${response.status} ${errorText}`) } @@ -1247,6 +1224,6 @@ export async function refreshOAuthToken( } } catch (error) { logger.error('Error refreshing token:', { error }) - throw error + return null } } diff --git a/apps/sim/tools/google_vault/types.ts b/apps/sim/tools/google_vault/types.ts index a9583f78ec..344bb02eaf 100644 --- a/apps/sim/tools/google_vault/types.ts +++ b/apps/sim/tools/google_vault/types.ts @@ -36,13 +36,11 @@ export type GoogleVaultCorpus = 'MAIL' | 'DRIVE' | 'GROUPS' | 'HANGOUTS_CHAT' | export interface GoogleVaultCreateMattersHoldsParams extends GoogleVaultCommonParams { holdName: string corpus: GoogleVaultCorpus - accountEmails?: string // Comma-separated list or array handled in the tool + accountEmails?: string orgUnitId?: string - // Query parameters for MAIL and GROUPS corpus (date filtering) terms?: string startTime?: string endTime?: string - // Drive-specific option includeSharedDrives?: boolean } From 713e3ae12f5c8dcdd4e8b15ec7766f087930da4f Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 16:03:12 -0800 Subject: [PATCH 04/11] restore --- apps/sim/app/api/auth/oauth/utils.ts | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 003c8b46ef..08dd16fdff 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -135,14 +135,11 @@ export async function getOAuthToken(userId: string, providerId: string): Promise const refreshResult = await refreshOAuthToken(providerId, credential.refreshToken!) if (!refreshResult) { - logger.error( - `Failed to refresh token for user ${userId}, provider ${providerId} - no result returned`, - { - providerId, - userId, - hasRefreshToken: !!credential.refreshToken, - } - ) + logger.error(`Failed to refresh token for user ${userId}, provider ${providerId}`, { + providerId, + userId, + hasRefreshToken: !!credential.refreshToken, + }) return null } @@ -224,15 +221,12 @@ export async function refreshAccessTokenIfNeeded( ) if (!refreshedToken) { - logger.error( - `[${requestId}] Failed to refresh token for credential: ${credentialId} - no result returned`, - { - credentialId, - providerId: credential.providerId, - userId: credential.userId, - hasRefreshToken: !!credential.refreshToken, - } - ) + logger.error(`[${requestId}] Failed to refresh token for credential: ${credentialId}`, { + credentialId, + providerId: credential.providerId, + userId: credential.userId, + hasRefreshToken: !!credential.refreshToken, + }) return null } From 6057a2c47a23b20503a0606c3b7bf346280010f9 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 16:17:19 -0800 Subject: [PATCH 05/11] added handler for vault --- .../google-vault/google-vault-handler.ts | 198 ++++++++++++++++++ apps/sim/executor/handlers/index.ts | 31 +++ apps/sim/executor/handlers/registry.ts | 2 + 3 files changed, 231 insertions(+) create mode 100644 apps/sim/executor/handlers/google-vault/google-vault-handler.ts create mode 100644 apps/sim/executor/handlers/index.ts diff --git a/apps/sim/executor/handlers/google-vault/google-vault-handler.ts b/apps/sim/executor/handlers/google-vault/google-vault-handler.ts new file mode 100644 index 0000000000..abdd86eb09 --- /dev/null +++ b/apps/sim/executor/handlers/google-vault/google-vault-handler.ts @@ -0,0 +1,198 @@ +/** + * Google Vault Block Handler + * + * Specialized handler for Google Vault blocks that provides enhanced error + * messages for credential-related issues specific to Google Vault's + * administrative requirements. + */ + +import { createLogger } from '@sim/logger' +import { getBlock } from '@/blocks/index' +import type { BlockHandler, ExecutionContext } from '@/executor/types' +import type { SerializedBlock } from '@/serializer/types' +import { executeTool } from '@/tools' +import { getTool } from '@/tools/utils' + +const logger = createLogger('GoogleVaultBlockHandler') + +/** + * Detects Google Vault credential/reauthentication errors + * These can manifest as: + * - RAPT (reauthentication policy) errors when Google Workspace admin requires reauth + * - Generic "failed to refresh token" errors which often wrap RAPT errors + */ +function isCredentialRefreshError(errorMessage: string): boolean { + const lowerMessage = errorMessage.toLowerCase() + return ( + lowerMessage.includes('invalid_rapt') || + lowerMessage.includes('reauth related error') || + (lowerMessage.includes('invalid_grant') && lowerMessage.includes('rapt')) || + lowerMessage.includes('failed to refresh token') || + (lowerMessage.includes('failed to fetch access token') && lowerMessage.includes('401')) + ) +} + +/** + * Enhances error messages for Google Vault credential failures + * Provides actionable workaround instructions for administrators + */ +function enhanceCredentialError(originalError: string): string { + if (isCredentialRefreshError(originalError)) { + return ( + `Google Vault authentication failed (likely due to reauthentication policy). ` + + `To resolve this, try disconnecting and reconnecting your Google Vault credential ` + + `in the Credentials settings. If the issue persists, ask your Google Workspace ` + + `administrator to disable "Reauthentication policy" for Sim Studio in the Google ` + + `Admin Console (Security > Access and data control > Context-Aware Access > ` + + `Reauthentication policy), or exempt Sim Studio from reauthentication requirements. ` + + `Learn more: https://support.google.com/a/answer/9368756` + ) + } + return originalError +} + +export class GoogleVaultBlockHandler implements BlockHandler { + canHandle(block: SerializedBlock): boolean { + return block.metadata?.id === 'google_vault' + } + + async execute( + ctx: ExecutionContext, + block: SerializedBlock, + inputs: Record + ): Promise { + const tool = getTool(block.config.tool) + if (!tool) { + throw new Error(`Tool not found: ${block.config.tool}`) + } + + let finalInputs = { ...inputs } + + const blockType = block.metadata?.id + if (blockType) { + const blockConfig = getBlock(blockType) + if (blockConfig?.tools?.config?.params) { + try { + const transformedParams = blockConfig.tools.config.params(inputs) + finalInputs = { ...inputs, ...transformedParams } + } catch (error) { + logger.warn(`Failed to apply parameter transformation for block type ${blockType}:`, { + error: error instanceof Error ? error.message : String(error), + }) + } + } + + if (blockConfig?.inputs) { + for (const [key, inputSchema] of Object.entries(blockConfig.inputs)) { + const value = finalInputs[key] + if (typeof value === 'string' && value.trim().length > 0) { + const inputType = typeof inputSchema === 'object' ? inputSchema.type : inputSchema + if (inputType === 'json' || inputType === 'array') { + try { + finalInputs[key] = JSON.parse(value.trim()) + } catch (error) { + logger.warn(`Failed to parse ${inputType} field "${key}":`, { + error: error instanceof Error ? error.message : String(error), + }) + } + } + } + } + } + } + + try { + const result = await executeTool( + block.config.tool, + { + ...finalInputs, + _context: { + workflowId: ctx.workflowId, + workspaceId: ctx.workspaceId, + executionId: ctx.executionId, + }, + }, + false, + false, + ctx + ) + + if (!result.success) { + const errorDetails = [] + if (result.error) { + // Enhance credential errors with Google Vault specific guidance + errorDetails.push(enhanceCredentialError(result.error)) + } + + const errorMessage = + errorDetails.length > 0 + ? errorDetails.join(' - ') + : `Block execution of ${tool?.name || block.config.tool} failed with no error message` + + const error = new Error(errorMessage) + + Object.assign(error, { + toolId: block.config.tool, + toolName: tool?.name || 'Unknown tool', + blockId: block.id, + blockName: block.metadata?.name || 'Unnamed Block', + output: result.output || {}, + timestamp: new Date().toISOString(), + }) + + throw error + } + + const output = result.output + let cost = null + + if (output?.cost) { + cost = output.cost + } + + if (cost) { + return { + ...output, + cost: { + input: cost.input, + output: cost.output, + total: cost.total, + }, + tokens: cost.tokens, + model: cost.model, + } + } + + return output + } catch (error: any) { + // Enhance credential errors thrown during tool execution + if (error instanceof Error) { + const enhancedMessage = enhanceCredentialError(error.message) + if (enhancedMessage !== error.message) { + error.message = enhancedMessage + } + } + + if (!error.message || error.message === 'undefined (undefined)') { + let errorMessage = `Block execution of ${tool?.name || block.config.tool} failed` + + if (block.metadata?.name) { + errorMessage += `: ${block.metadata.name}` + } + + if (error.status) { + errorMessage += ` (Status: ${error.status})` + } + + error.message = errorMessage + } + + if (typeof error === 'object' && error !== null) { + if (!error.toolId) error.toolId = block.config.tool + if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block' + } + + throw error + } + } +} diff --git a/apps/sim/executor/handlers/index.ts b/apps/sim/executor/handlers/index.ts new file mode 100644 index 0000000000..fbb2f661ce --- /dev/null +++ b/apps/sim/executor/handlers/index.ts @@ -0,0 +1,31 @@ +import { AgentBlockHandler } from '@/executor/handlers/agent/agent-handler' +import { ApiBlockHandler } from '@/executor/handlers/api/api-handler' +import { ConditionBlockHandler } from '@/executor/handlers/condition/condition-handler' +import { EvaluatorBlockHandler } from '@/executor/handlers/evaluator/evaluator-handler' +import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler' +import { GenericBlockHandler } from '@/executor/handlers/generic/generic-handler' +import { GoogleVaultBlockHandler } from '@/executor/handlers/google-vault/google-vault-handler' +import { HumanInTheLoopBlockHandler } from '@/executor/handlers/human-in-the-loop/human-in-the-loop-handler' +import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler' +import { RouterBlockHandler } from '@/executor/handlers/router/router-handler' +import { TriggerBlockHandler } from '@/executor/handlers/trigger/trigger-handler' +import { VariablesBlockHandler } from '@/executor/handlers/variables/variables-handler' +import { WaitBlockHandler } from '@/executor/handlers/wait/wait-handler' +import { WorkflowBlockHandler } from '@/executor/handlers/workflow/workflow-handler' + +export { + AgentBlockHandler, + ApiBlockHandler, + ConditionBlockHandler, + EvaluatorBlockHandler, + FunctionBlockHandler, + GenericBlockHandler, + GoogleVaultBlockHandler, + ResponseBlockHandler, + HumanInTheLoopBlockHandler, + RouterBlockHandler, + TriggerBlockHandler, + VariablesBlockHandler, + WaitBlockHandler, + WorkflowBlockHandler, +} diff --git a/apps/sim/executor/handlers/registry.ts b/apps/sim/executor/handlers/registry.ts index 9e977668a2..31bcbe57f7 100644 --- a/apps/sim/executor/handlers/registry.ts +++ b/apps/sim/executor/handlers/registry.ts @@ -11,6 +11,7 @@ import { ConditionBlockHandler } from '@/executor/handlers/condition/condition-h import { EvaluatorBlockHandler } from '@/executor/handlers/evaluator/evaluator-handler' import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler' import { GenericBlockHandler } from '@/executor/handlers/generic/generic-handler' +import { GoogleVaultBlockHandler } from '@/executor/handlers/google-vault/google-vault-handler' import { HumanInTheLoopBlockHandler } from '@/executor/handlers/human-in-the-loop/human-in-the-loop-handler' import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler' import { RouterBlockHandler } from '@/executor/handlers/router/router-handler' @@ -40,6 +41,7 @@ export function createBlockHandlers(): BlockHandler[] { new WorkflowBlockHandler(), new WaitBlockHandler(), new EvaluatorBlockHandler(), + new GoogleVaultBlockHandler(), new GenericBlockHandler(), ] } From e7c47eb2eff6c9ea2a04bdeae17cbae1ffc7f7c0 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 16:18:21 -0800 Subject: [PATCH 06/11] updated docs --- .../docs/content/docs/en/tools/google_vault.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/docs/content/docs/en/tools/google_vault.mdx b/apps/docs/content/docs/en/tools/google_vault.mdx index 1dde5f9e48..13ec63fed6 100644 --- a/apps/docs/content/docs/en/tools/google_vault.mdx +++ b/apps/docs/content/docs/en/tools/google_vault.mdx @@ -40,6 +40,14 @@ Connect Google Vault to create exports, list exports, and manage holds within ma | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `matterId` | string | Yes | No description | +| `exportName` | string | Yes | No description | +| `corpus` | string | Yes | Data corpus to export \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) | +| `accountEmails` | string | No | Comma-separated list of user emails to scope export | +| `orgUnitId` | string | No | Organization unit ID to scope export \(alternative to emails\) | +| `startTime` | string | No | Start time for date filtering \(ISO 8601 format, e.g., 2024-01-01T00:00:00Z\) | +| `endTime` | string | No | End time for date filtering \(ISO 8601 format, e.g., 2024-12-31T23:59:59Z\) | +| `terms` | string | No | Search query terms to filter exported content | #### Output @@ -99,6 +107,15 @@ Download a single file from a Google Vault export (GCS object) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `matterId` | string | Yes | No description | +| `holdName` | string | Yes | No description | +| `corpus` | string | Yes | Data corpus to hold \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) | +| `accountEmails` | string | No | Comma-separated list of user emails to put on hold | +| `orgUnitId` | string | No | Organization unit ID to put on hold \(alternative to accounts\) | +| `terms` | string | No | Search terms to filter held content \(for MAIL and GROUPS corpus\) | +| `startTime` | string | No | Start time for date filtering \(ISO 8601 format, for MAIL and GROUPS corpus\) | +| `endTime` | string | No | End time for date filtering \(ISO 8601 format, for MAIL and GROUPS corpus\) | +| `includeSharedDrives` | boolean | No | Include files in shared drives \(for DRIVE corpus\) | #### Output From f46f9977b7a3980e5ffca36ed8ad2af02d32bd64 Mon Sep 17 00:00:00 2001 From: aadamgough Date: Thu, 8 Jan 2026 16:19:40 -0800 Subject: [PATCH 07/11] restored --- apps/docs/content/docs/en/tools/translate.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/docs/content/docs/en/tools/translate.mdx b/apps/docs/content/docs/en/tools/translate.mdx index 3c3f7dc75d..a1959d9396 100644 --- a/apps/docs/content/docs/en/tools/translate.mdx +++ b/apps/docs/content/docs/en/tools/translate.mdx @@ -53,9 +53,6 @@ Send a chat completion request to any supported LLM provider | `vertexProject` | string | No | Google Cloud project ID for Vertex AI | | `vertexLocation` | string | No | Google Cloud location for Vertex AI \(defaults to us-central1\) | | `vertexCredential` | string | No | Google Cloud OAuth credential ID for Vertex AI | -| `bedrockAccessKeyId` | string | No | AWS Access Key ID for Bedrock | -| `bedrockSecretKey` | string | No | AWS Secret Access Key for Bedrock | -| `bedrockRegion` | string | No | AWS region for Bedrock \(defaults to us-east-1\) | #### Output From 79352e296360832d06d560258cf8f02e844ed9ca Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 8 Jan 2026 23:31:12 -0800 Subject: [PATCH 08/11] removed google vault from executor --- .../google-vault/google-vault-handler.ts | 198 ------------------ apps/sim/executor/handlers/index.ts | 2 - apps/sim/executor/handlers/registry.ts | 2 - apps/sim/tools/google_vault/create_matters.ts | 13 +- .../google_vault/create_matters_export.ts | 7 +- .../google_vault/create_matters_holds.ts | 12 +- .../google_vault/download_export_file.ts | 29 +-- apps/sim/tools/google_vault/list_matters.ts | 12 +- .../tools/google_vault/list_matters_export.ts | 4 +- .../tools/google_vault/list_matters_holds.ts | 4 +- apps/sim/tools/google_vault/types.ts | 30 ++- apps/sim/tools/google_vault/utils.ts | 41 ++++ 12 files changed, 94 insertions(+), 260 deletions(-) delete mode 100644 apps/sim/executor/handlers/google-vault/google-vault-handler.ts create mode 100644 apps/sim/tools/google_vault/utils.ts diff --git a/apps/sim/executor/handlers/google-vault/google-vault-handler.ts b/apps/sim/executor/handlers/google-vault/google-vault-handler.ts deleted file mode 100644 index abdd86eb09..0000000000 --- a/apps/sim/executor/handlers/google-vault/google-vault-handler.ts +++ /dev/null @@ -1,198 +0,0 @@ -/** - * Google Vault Block Handler - * - * Specialized handler for Google Vault blocks that provides enhanced error - * messages for credential-related issues specific to Google Vault's - * administrative requirements. - */ - -import { createLogger } from '@sim/logger' -import { getBlock } from '@/blocks/index' -import type { BlockHandler, ExecutionContext } from '@/executor/types' -import type { SerializedBlock } from '@/serializer/types' -import { executeTool } from '@/tools' -import { getTool } from '@/tools/utils' - -const logger = createLogger('GoogleVaultBlockHandler') - -/** - * Detects Google Vault credential/reauthentication errors - * These can manifest as: - * - RAPT (reauthentication policy) errors when Google Workspace admin requires reauth - * - Generic "failed to refresh token" errors which often wrap RAPT errors - */ -function isCredentialRefreshError(errorMessage: string): boolean { - const lowerMessage = errorMessage.toLowerCase() - return ( - lowerMessage.includes('invalid_rapt') || - lowerMessage.includes('reauth related error') || - (lowerMessage.includes('invalid_grant') && lowerMessage.includes('rapt')) || - lowerMessage.includes('failed to refresh token') || - (lowerMessage.includes('failed to fetch access token') && lowerMessage.includes('401')) - ) -} - -/** - * Enhances error messages for Google Vault credential failures - * Provides actionable workaround instructions for administrators - */ -function enhanceCredentialError(originalError: string): string { - if (isCredentialRefreshError(originalError)) { - return ( - `Google Vault authentication failed (likely due to reauthentication policy). ` + - `To resolve this, try disconnecting and reconnecting your Google Vault credential ` + - `in the Credentials settings. If the issue persists, ask your Google Workspace ` + - `administrator to disable "Reauthentication policy" for Sim Studio in the Google ` + - `Admin Console (Security > Access and data control > Context-Aware Access > ` + - `Reauthentication policy), or exempt Sim Studio from reauthentication requirements. ` + - `Learn more: https://support.google.com/a/answer/9368756` - ) - } - return originalError -} - -export class GoogleVaultBlockHandler implements BlockHandler { - canHandle(block: SerializedBlock): boolean { - return block.metadata?.id === 'google_vault' - } - - async execute( - ctx: ExecutionContext, - block: SerializedBlock, - inputs: Record - ): Promise { - const tool = getTool(block.config.tool) - if (!tool) { - throw new Error(`Tool not found: ${block.config.tool}`) - } - - let finalInputs = { ...inputs } - - const blockType = block.metadata?.id - if (blockType) { - const blockConfig = getBlock(blockType) - if (blockConfig?.tools?.config?.params) { - try { - const transformedParams = blockConfig.tools.config.params(inputs) - finalInputs = { ...inputs, ...transformedParams } - } catch (error) { - logger.warn(`Failed to apply parameter transformation for block type ${blockType}:`, { - error: error instanceof Error ? error.message : String(error), - }) - } - } - - if (blockConfig?.inputs) { - for (const [key, inputSchema] of Object.entries(blockConfig.inputs)) { - const value = finalInputs[key] - if (typeof value === 'string' && value.trim().length > 0) { - const inputType = typeof inputSchema === 'object' ? inputSchema.type : inputSchema - if (inputType === 'json' || inputType === 'array') { - try { - finalInputs[key] = JSON.parse(value.trim()) - } catch (error) { - logger.warn(`Failed to parse ${inputType} field "${key}":`, { - error: error instanceof Error ? error.message : String(error), - }) - } - } - } - } - } - } - - try { - const result = await executeTool( - block.config.tool, - { - ...finalInputs, - _context: { - workflowId: ctx.workflowId, - workspaceId: ctx.workspaceId, - executionId: ctx.executionId, - }, - }, - false, - false, - ctx - ) - - if (!result.success) { - const errorDetails = [] - if (result.error) { - // Enhance credential errors with Google Vault specific guidance - errorDetails.push(enhanceCredentialError(result.error)) - } - - const errorMessage = - errorDetails.length > 0 - ? errorDetails.join(' - ') - : `Block execution of ${tool?.name || block.config.tool} failed with no error message` - - const error = new Error(errorMessage) - - Object.assign(error, { - toolId: block.config.tool, - toolName: tool?.name || 'Unknown tool', - blockId: block.id, - blockName: block.metadata?.name || 'Unnamed Block', - output: result.output || {}, - timestamp: new Date().toISOString(), - }) - - throw error - } - - const output = result.output - let cost = null - - if (output?.cost) { - cost = output.cost - } - - if (cost) { - return { - ...output, - cost: { - input: cost.input, - output: cost.output, - total: cost.total, - }, - tokens: cost.tokens, - model: cost.model, - } - } - - return output - } catch (error: any) { - // Enhance credential errors thrown during tool execution - if (error instanceof Error) { - const enhancedMessage = enhanceCredentialError(error.message) - if (enhancedMessage !== error.message) { - error.message = enhancedMessage - } - } - - if (!error.message || error.message === 'undefined (undefined)') { - let errorMessage = `Block execution of ${tool?.name || block.config.tool} failed` - - if (block.metadata?.name) { - errorMessage += `: ${block.metadata.name}` - } - - if (error.status) { - errorMessage += ` (Status: ${error.status})` - } - - error.message = errorMessage - } - - if (typeof error === 'object' && error !== null) { - if (!error.toolId) error.toolId = block.config.tool - if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block' - } - - throw error - } - } -} diff --git a/apps/sim/executor/handlers/index.ts b/apps/sim/executor/handlers/index.ts index fbb2f661ce..fb27421b2d 100644 --- a/apps/sim/executor/handlers/index.ts +++ b/apps/sim/executor/handlers/index.ts @@ -4,7 +4,6 @@ import { ConditionBlockHandler } from '@/executor/handlers/condition/condition-h import { EvaluatorBlockHandler } from '@/executor/handlers/evaluator/evaluator-handler' import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler' import { GenericBlockHandler } from '@/executor/handlers/generic/generic-handler' -import { GoogleVaultBlockHandler } from '@/executor/handlers/google-vault/google-vault-handler' import { HumanInTheLoopBlockHandler } from '@/executor/handlers/human-in-the-loop/human-in-the-loop-handler' import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler' import { RouterBlockHandler } from '@/executor/handlers/router/router-handler' @@ -20,7 +19,6 @@ export { EvaluatorBlockHandler, FunctionBlockHandler, GenericBlockHandler, - GoogleVaultBlockHandler, ResponseBlockHandler, HumanInTheLoopBlockHandler, RouterBlockHandler, diff --git a/apps/sim/executor/handlers/registry.ts b/apps/sim/executor/handlers/registry.ts index 31bcbe57f7..9e977668a2 100644 --- a/apps/sim/executor/handlers/registry.ts +++ b/apps/sim/executor/handlers/registry.ts @@ -11,7 +11,6 @@ import { ConditionBlockHandler } from '@/executor/handlers/condition/condition-h import { EvaluatorBlockHandler } from '@/executor/handlers/evaluator/evaluator-handler' import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler' import { GenericBlockHandler } from '@/executor/handlers/generic/generic-handler' -import { GoogleVaultBlockHandler } from '@/executor/handlers/google-vault/google-vault-handler' import { HumanInTheLoopBlockHandler } from '@/executor/handlers/human-in-the-loop/human-in-the-loop-handler' import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler' import { RouterBlockHandler } from '@/executor/handlers/router/router-handler' @@ -41,7 +40,6 @@ export function createBlockHandlers(): BlockHandler[] { new WorkflowBlockHandler(), new WaitBlockHandler(), new EvaluatorBlockHandler(), - new GoogleVaultBlockHandler(), new GenericBlockHandler(), ] } diff --git a/apps/sim/tools/google_vault/create_matters.ts b/apps/sim/tools/google_vault/create_matters.ts index 9886606656..ccbfc587e7 100644 --- a/apps/sim/tools/google_vault/create_matters.ts +++ b/apps/sim/tools/google_vault/create_matters.ts @@ -1,13 +1,7 @@ +import type { GoogleVaultCreateMattersParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' -export interface GoogleVaultCreateMattersParams { - accessToken: string - name: string - description?: string -} - -// matters.create -// POST https://vault.googleapis.com/v1/matters export const createMattersTool: ToolConfig = { id: 'create_matters', name: 'Vault Create Matter', @@ -38,7 +32,8 @@ export const createMattersTool: ToolConfig = { transformResponse: async (response: Response) => { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to create matter') + const errorMessage = data.error?.message || 'Failed to create matter' + throw new Error(enhanceGoogleVaultError(errorMessage)) } return { success: true, output: { matter: data } } }, diff --git a/apps/sim/tools/google_vault/create_matters_export.ts b/apps/sim/tools/google_vault/create_matters_export.ts index 3f443ce6d6..d20432eb6e 100644 --- a/apps/sim/tools/google_vault/create_matters_export.ts +++ b/apps/sim/tools/google_vault/create_matters_export.ts @@ -1,8 +1,7 @@ import type { GoogleVaultCreateMattersExportParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' -// matters.exports.create -// POST https://vault.googleapis.com/v1/matters/{matterId}/exports export const createMattersExportTool: ToolConfig = { id: 'create_matters_export', name: 'Vault Create Export (by Matter)', @@ -64,7 +63,6 @@ export const createMattersExportTool: ToolConfig { - // Handle accountEmails - can be string (comma-separated) or array let emails: string[] = [] if (params.accountEmails) { if (Array.isArray(params.accountEmails)) { @@ -106,7 +104,8 @@ export const createMattersExportTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to create export') + const errorMessage = data.error?.message || 'Failed to create export' + throw new Error(enhanceGoogleVaultError(errorMessage)) } return { success: true, output: { export: data } } }, diff --git a/apps/sim/tools/google_vault/create_matters_holds.ts b/apps/sim/tools/google_vault/create_matters_holds.ts index 8c464edb4f..021f50101a 100644 --- a/apps/sim/tools/google_vault/create_matters_holds.ts +++ b/apps/sim/tools/google_vault/create_matters_holds.ts @@ -1,8 +1,7 @@ import type { GoogleVaultCreateMattersHoldsParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' -// matters.holds.create -// POST https://vault.googleapis.com/v1/matters/{matterId}/holds export const createMattersHoldsTool: ToolConfig = { id: 'create_matters_holds', name: 'Vault Create Hold (by Matter)', @@ -36,7 +35,6 @@ export const createMattersHoldsTool: ToolConfig { - // Build Hold body. One of accounts or orgUnit must be provided. const body: any = { name: params.holdName, corpus: params.corpus, } - // Handle accountEmails - can be string (comma-separated) or array let emails: string[] = [] if (params.accountEmails) { if (Array.isArray(params.accountEmails)) { @@ -92,13 +87,11 @@ export const createMattersHoldsTool: ToolConfig 0) { - // Google Vault expects HeldAccount objects with 'email' or 'accountId'. Use 'email' here. body.accounts = emails.map((email: string) => ({ email })) } else if (params.orgUnitId) { body.orgUnit = { orgUnitId: params.orgUnitId } } - // Build corpus-specific query for date filtering if (params.corpus === 'MAIL' || params.corpus === 'GROUPS') { const hasQueryParams = params.terms || params.startTime || params.endTime if (hasQueryParams) { @@ -124,7 +117,8 @@ export const createMattersHoldsTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to create hold') + const errorMessage = data.error?.message || 'Failed to create hold' + throw new Error(enhanceGoogleVaultError(errorMessage)) } return { success: true, output: { hold: data } } }, diff --git a/apps/sim/tools/google_vault/download_export_file.ts b/apps/sim/tools/google_vault/download_export_file.ts index e63bd71cd9..ce16dcdcc2 100644 --- a/apps/sim/tools/google_vault/download_export_file.ts +++ b/apps/sim/tools/google_vault/download_export_file.ts @@ -1,17 +1,8 @@ -import { createLogger } from '@sim/logger' +import type { GoogleVaultDownloadExportFileParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' -const logger = createLogger('GoogleVaultDownloadExportFileTool') - -interface DownloadParams { - accessToken: string - matterId: string - bucketName: string - objectName: string - fileName?: string -} - -export const downloadExportFileTool: ToolConfig = { +export const downloadExportFileTool: ToolConfig = { id: 'google_vault_download_export_file', name: 'Vault Download Export File', description: 'Download a single file from a Google Vault export (GCS object)', @@ -34,17 +25,15 @@ export const downloadExportFileTool: ToolConfig = { url: (params) => { const bucket = encodeURIComponent(params.bucketName) const object = encodeURIComponent(params.objectName) - // Use GCS media endpoint directly; framework will prefetch token and inject accessToken return `https://storage.googleapis.com/storage/v1/b/${bucket}/o/${object}?alt=media` }, method: 'GET', headers: (params) => ({ - // Access token is injected by the tools framework when 'credential' is present Authorization: `Bearer ${params.accessToken}`, }), }, - transformResponse: async (response: Response, params?: DownloadParams) => { + transformResponse: async (response: Response, params?: GoogleVaultDownloadExportFileParams) => { if (!response.ok) { let details: any try { @@ -57,10 +46,11 @@ export const downloadExportFileTool: ToolConfig = { details = undefined } } - throw new Error(details?.error || `Failed to download Vault export file (${response.status})`) + const errorMessage = + details?.error || `Failed to download Vault export file (${response.status})` + throw new Error(enhanceGoogleVaultError(errorMessage)) } - // Since we're just doing a HEAD request to verify access, we need to fetch the actual file if (!params?.accessToken || !params?.bucketName || !params?.objectName) { throw new Error('Missing required parameters for download') } @@ -69,7 +59,6 @@ export const downloadExportFileTool: ToolConfig = { const object = encodeURIComponent(params.objectName) const downloadUrl = `https://storage.googleapis.com/storage/v1/b/${bucket}/o/${object}?alt=media` - // Fetch the actual file content const downloadResponse = await fetch(downloadUrl, { method: 'GET', headers: { @@ -79,7 +68,8 @@ export const downloadExportFileTool: ToolConfig = { if (!downloadResponse.ok) { const errorText = await downloadResponse.text().catch(() => '') - throw new Error(`Failed to download file: ${errorText || downloadResponse.statusText}`) + const errorMessage = `Failed to download file: ${errorText || downloadResponse.statusText}` + throw new Error(enhanceGoogleVaultError(errorMessage)) } const contentType = downloadResponse.headers.get('content-type') || 'application/octet-stream' @@ -104,7 +94,6 @@ export const downloadExportFileTool: ToolConfig = { } } - // Get the file as an array buffer and convert to Buffer const arrayBuffer = await downloadResponse.arrayBuffer() const buffer = Buffer.from(arrayBuffer) diff --git a/apps/sim/tools/google_vault/list_matters.ts b/apps/sim/tools/google_vault/list_matters.ts index d5182aacc2..9edbd80564 100644 --- a/apps/sim/tools/google_vault/list_matters.ts +++ b/apps/sim/tools/google_vault/list_matters.ts @@ -1,12 +1,7 @@ +import type { GoogleVaultListMattersParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' -export interface GoogleVaultListMattersParams { - accessToken: string - pageSize?: number - pageToken?: string - matterId?: string // Optional get for a specific matter -} - export const listMattersTool: ToolConfig = { id: 'list_matters', name: 'Vault List Matters', @@ -47,7 +42,8 @@ export const listMattersTool: ToolConfig = { transformResponse: async (response: Response, params?: GoogleVaultListMattersParams) => { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to list matters') + const errorMessage = data.error?.message || 'Failed to list matters' + throw new Error(enhanceGoogleVaultError(errorMessage)) } if (params?.matterId) { return { success: true, output: { matter: data } } diff --git a/apps/sim/tools/google_vault/list_matters_export.ts b/apps/sim/tools/google_vault/list_matters_export.ts index 539ccd11d2..fbfcfd1092 100644 --- a/apps/sim/tools/google_vault/list_matters_export.ts +++ b/apps/sim/tools/google_vault/list_matters_export.ts @@ -1,4 +1,5 @@ import type { GoogleVaultListMattersExportParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const listMattersExportTool: ToolConfig = { @@ -42,7 +43,8 @@ export const listMattersExportTool: ToolConfig { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to list exports') + const errorMessage = data.error?.message || 'Failed to list exports' + throw new Error(enhanceGoogleVaultError(errorMessage)) } if (params?.exportId) { return { success: true, output: { export: data } } diff --git a/apps/sim/tools/google_vault/list_matters_holds.ts b/apps/sim/tools/google_vault/list_matters_holds.ts index 6f7a90cdce..f0134a4afb 100644 --- a/apps/sim/tools/google_vault/list_matters_holds.ts +++ b/apps/sim/tools/google_vault/list_matters_holds.ts @@ -1,4 +1,5 @@ import type { GoogleVaultListMattersHoldsParams } from '@/tools/google_vault/types' +import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const listMattersHoldsTool: ToolConfig = { @@ -42,7 +43,8 @@ export const listMattersHoldsTool: ToolConfig transformResponse: async (response: Response, params?: GoogleVaultListMattersHoldsParams) => { const data = await response.json() if (!response.ok) { - throw new Error(data.error?.message || 'Failed to list holds') + const errorMessage = data.error?.message || 'Failed to list holds' + throw new Error(enhanceGoogleVaultError(errorMessage)) } if (params?.holdId) { return { success: true, output: { hold: data } } diff --git a/apps/sim/tools/google_vault/types.ts b/apps/sim/tools/google_vault/types.ts index 344bb02eaf..a94ff39819 100644 --- a/apps/sim/tools/google_vault/types.ts +++ b/apps/sim/tools/google_vault/types.ts @@ -5,11 +5,31 @@ export interface GoogleVaultCommonParams { matterId: string } -// Exports +export interface GoogleVaultCreateMattersParams { + accessToken: string + name: string + description?: string +} + +export interface GoogleVaultListMattersParams { + accessToken: string + pageSize?: number + pageToken?: string + matterId?: string +} + +export interface GoogleVaultDownloadExportFileParams { + accessToken: string + matterId: string + bucketName: string + objectName: string + fileName?: string +} + export interface GoogleVaultCreateMattersExportParams extends GoogleVaultCommonParams { exportName: string corpus: GoogleVaultCorpus - accountEmails?: string // Comma-separated list or array handled in the tool + accountEmails?: string orgUnitId?: string terms?: string startTime?: string @@ -20,15 +40,13 @@ export interface GoogleVaultCreateMattersExportParams extends GoogleVaultCommonP export interface GoogleVaultListMattersExportParams extends GoogleVaultCommonParams { pageSize?: number pageToken?: string - exportId?: string // Short input to fetch a specific export + exportId?: string } export interface GoogleVaultListMattersExportResponse extends ToolResponse { output: any } -// Holds -// Simplified: default to BASIC_HOLD by omission in requests export type GoogleVaultHoldView = 'BASIC_HOLD' | 'FULL_HOLD' export type GoogleVaultCorpus = 'MAIL' | 'DRIVE' | 'GROUPS' | 'HANGOUTS_CHAT' | 'VOICE' @@ -47,7 +65,7 @@ export interface GoogleVaultCreateMattersHoldsParams extends GoogleVaultCommonPa export interface GoogleVaultListMattersHoldsParams extends GoogleVaultCommonParams { pageSize?: number pageToken?: string - holdId?: string // Short input to fetch a specific hold + holdId?: string } export interface GoogleVaultListMattersHoldsResponse extends ToolResponse { diff --git a/apps/sim/tools/google_vault/utils.ts b/apps/sim/tools/google_vault/utils.ts new file mode 100644 index 0000000000..6fdd40ebee --- /dev/null +++ b/apps/sim/tools/google_vault/utils.ts @@ -0,0 +1,41 @@ +/** + * Google Vault Error Enhancement Utilities + * + * Provides user-friendly error messages for common Google Vault authentication + * and credential issues, particularly RAPT (reauthentication policy) errors. + */ + +/** + * Detects if an error message indicates a credential/reauthentication issue + */ +function isCredentialRefreshError(errorMessage: string): boolean { + const lowerMessage = errorMessage.toLowerCase() + return ( + lowerMessage.includes('invalid_rapt') || + lowerMessage.includes('reauth related error') || + (lowerMessage.includes('invalid_grant') && lowerMessage.includes('rapt')) || + lowerMessage.includes('failed to refresh token') || + (lowerMessage.includes('failed to fetch access token') && lowerMessage.includes('401')) + ) +} + +/** + * Enhances Google Vault error messages with actionable guidance + * + * For credential/reauthentication errors (RAPT errors), provides specific + * instructions for resolving the issue through Google Admin Console settings. + */ +export function enhanceGoogleVaultError(errorMessage: string): string { + if (isCredentialRefreshError(errorMessage)) { + return ( + `Google Vault authentication failed (likely due to reauthentication policy). ` + + `To resolve this, try disconnecting and reconnecting your Google Vault credential ` + + `in the Credentials settings. If the issue persists, ask your Google Workspace ` + + `administrator to disable "Reauthentication policy" for Sim Studio in the Google ` + + `Admin Console (Security > Access and data control > Context-Aware Access > ` + + `Reauthentication policy), or exempt Sim Studio from reauthentication requirements. ` + + `Learn more: https://support.google.com/a/answer/9368756` + ) + } + return errorMessage +} From ec75de37df3ef776f52859a7eff2c7e7b7860c3f Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 8 Jan 2026 23:32:59 -0800 Subject: [PATCH 09/11] updated translations --- apps/docs/content/docs/en/tools/translate.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/docs/content/docs/en/tools/translate.mdx b/apps/docs/content/docs/en/tools/translate.mdx index a1959d9396..3c3f7dc75d 100644 --- a/apps/docs/content/docs/en/tools/translate.mdx +++ b/apps/docs/content/docs/en/tools/translate.mdx @@ -53,6 +53,9 @@ Send a chat completion request to any supported LLM provider | `vertexProject` | string | No | Google Cloud project ID for Vertex AI | | `vertexLocation` | string | No | Google Cloud location for Vertex AI \(defaults to us-central1\) | | `vertexCredential` | string | No | Google Cloud OAuth credential ID for Vertex AI | +| `bedrockAccessKeyId` | string | No | AWS Access Key ID for Bedrock | +| `bedrockSecretKey` | string | No | AWS Secret Access Key for Bedrock | +| `bedrockRegion` | string | No | AWS region for Bedrock \(defaults to us-east-1\) | #### Output From f40499feba5b1a18bc0f2ec310ff7de3e183db3d Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 16 Jan 2026 16:32:20 -0800 Subject: [PATCH 10/11] updated docs --- .../content/docs/en/tools/google_vault.mdx | 85 ++++++++----------- apps/sim/tools/google_vault/create_matters.ts | 2 +- .../google_vault/create_matters_export.ts | 2 +- .../google_vault/create_matters_holds.ts | 2 +- apps/sim/tools/google_vault/list_matters.ts | 2 +- .../tools/google_vault/list_matters_export.ts | 2 +- .../tools/google_vault/list_matters_holds.ts | 2 +- 7 files changed, 43 insertions(+), 54 deletions(-) diff --git a/apps/docs/content/docs/en/tools/google_vault.mdx b/apps/docs/content/docs/en/tools/google_vault.mdx index 13ec63fed6..7a7ece8cf3 100644 --- a/apps/docs/content/docs/en/tools/google_vault.mdx +++ b/apps/docs/content/docs/en/tools/google_vault.mdx @@ -36,6 +36,8 @@ Connect Google Vault to create exports, list exports, and manage holds within ma ### `google_vault_create_matters_export` +Create an export in a matter + #### Input | Parameter | Type | Required | Description | @@ -53,34 +55,28 @@ Connect Google Vault to create exports, list exports, and manage holds within ma | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `export` | json | Created export object | ### `google_vault_list_matters_export` +List exports for a matter + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `matterId` | string | Yes | No description | +| `pageSize` | number | No | No description | +| `pageToken` | string | No | No description | +| `exportId` | string | No | No description | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `exports` | json | Array of export objects | +| `export` | json | Single export object \(when exportId is provided\) | +| `nextPageToken` | string | Token for fetching next page of results | ### `google_vault_download_export_file` @@ -103,6 +99,8 @@ Download a single file from a Google Vault export (GCS object) ### `google_vault_create_matters_holds` +Create a hold in a matter + #### Input | Parameter | Type | Required | Description | @@ -121,73 +119,64 @@ Download a single file from a Google Vault export (GCS object) | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `hold` | json | Created hold object | ### `google_vault_list_matters_holds` +List holds for a matter + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `matterId` | string | Yes | No description | +| `pageSize` | number | No | No description | +| `pageToken` | string | No | No description | +| `holdId` | string | No | No description | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `holds` | json | Array of hold objects | +| `hold` | json | Single hold object \(when holdId is provided\) | +| `nextPageToken` | string | Token for fetching next page of results | ### `google_vault_create_matters` +Create a new matter in Google Vault + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `name` | string | Yes | No description | +| `description` | string | No | No description | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `matter` | json | Created matter object | ### `google_vault_list_matters` +List matters, or get a specific matter if matterId is provided + #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `pageSize` | number | No | No description | +| `pageToken` | string | No | No description | +| `matterId` | string | No | No description | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `matters` | json | Array of matter objects \(for list_matters\) | -| `exports` | json | Array of export objects \(for list_matters_export\) | -| `holds` | json | Array of hold objects \(for list_matters_holds\) | -| `matter` | json | Created matter object \(for create_matters\) | -| `export` | json | Created export object \(for create_matters_export\) | -| `hold` | json | Created hold object \(for create_matters_holds\) | -| `file` | json | Downloaded export file \(UserFile\) from execution files | -| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) | +| `matters` | json | Array of matter objects | +| `matter` | json | Single matter object \(when matterId is provided\) | +| `nextPageToken` | string | Token for fetching next page of results | diff --git a/apps/sim/tools/google_vault/create_matters.ts b/apps/sim/tools/google_vault/create_matters.ts index ccbfc587e7..23aa90112b 100644 --- a/apps/sim/tools/google_vault/create_matters.ts +++ b/apps/sim/tools/google_vault/create_matters.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const createMattersTool: ToolConfig = { - id: 'create_matters', + id: 'google_vault_create_matters', name: 'Vault Create Matter', description: 'Create a new matter in Google Vault', version: '1.0', diff --git a/apps/sim/tools/google_vault/create_matters_export.ts b/apps/sim/tools/google_vault/create_matters_export.ts index d20432eb6e..2b93a6b586 100644 --- a/apps/sim/tools/google_vault/create_matters_export.ts +++ b/apps/sim/tools/google_vault/create_matters_export.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const createMattersExportTool: ToolConfig = { - id: 'create_matters_export', + id: 'google_vault_create_matters_export', name: 'Vault Create Export (by Matter)', description: 'Create an export in a matter', version: '1.0', diff --git a/apps/sim/tools/google_vault/create_matters_holds.ts b/apps/sim/tools/google_vault/create_matters_holds.ts index 021f50101a..39f0d310af 100644 --- a/apps/sim/tools/google_vault/create_matters_holds.ts +++ b/apps/sim/tools/google_vault/create_matters_holds.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const createMattersHoldsTool: ToolConfig = { - id: 'create_matters_holds', + id: 'google_vault_create_matters_holds', name: 'Vault Create Hold (by Matter)', description: 'Create a hold in a matter', version: '1.0', diff --git a/apps/sim/tools/google_vault/list_matters.ts b/apps/sim/tools/google_vault/list_matters.ts index 9edbd80564..b7fd5c2565 100644 --- a/apps/sim/tools/google_vault/list_matters.ts +++ b/apps/sim/tools/google_vault/list_matters.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const listMattersTool: ToolConfig = { - id: 'list_matters', + id: 'google_vault_list_matters', name: 'Vault List Matters', description: 'List matters, or get a specific matter if matterId is provided', version: '1.0', diff --git a/apps/sim/tools/google_vault/list_matters_export.ts b/apps/sim/tools/google_vault/list_matters_export.ts index fbfcfd1092..41429d2047 100644 --- a/apps/sim/tools/google_vault/list_matters_export.ts +++ b/apps/sim/tools/google_vault/list_matters_export.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const listMattersExportTool: ToolConfig = { - id: 'list_matters_export', + id: 'google_vault_list_matters_export', name: 'Vault List Exports (by Matter)', description: 'List exports for a matter', version: '1.0', diff --git a/apps/sim/tools/google_vault/list_matters_holds.ts b/apps/sim/tools/google_vault/list_matters_holds.ts index f0134a4afb..08aa12747e 100644 --- a/apps/sim/tools/google_vault/list_matters_holds.ts +++ b/apps/sim/tools/google_vault/list_matters_holds.ts @@ -3,7 +3,7 @@ import { enhanceGoogleVaultError } from '@/tools/google_vault/utils' import type { ToolConfig } from '@/tools/types' export const listMattersHoldsTool: ToolConfig = { - id: 'list_matters_holds', + id: 'google_vault_list_matters_holds', name: 'Vault List Holds (by Matter)', description: 'List holds for a matter', version: '1.0', From 19469b8cd2ef42e1738cb2c4f089426b03afd7cf Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 16 Jan 2026 16:48:05 -0800 Subject: [PATCH 11/11] fixed inputs and outputs --- .../content/docs/en/tools/google_vault.mdx | 42 +++++++------- apps/sim/blocks/blocks/google_vault.ts | 58 +++++++++++++++---- apps/sim/tools/google_vault/create_matters.ts | 21 ++++++- .../google_vault/create_matters_export.ts | 23 ++++++-- .../google_vault/create_matters_holds.ts | 23 ++++++-- .../google_vault/download_export_file.ts | 35 +++++++++-- apps/sim/tools/google_vault/list_matters.ts | 28 +++++++-- .../tools/google_vault/list_matters_export.ts | 37 ++++++++++-- .../tools/google_vault/list_matters_holds.ts | 37 ++++++++++-- 9 files changed, 240 insertions(+), 64 deletions(-) diff --git a/apps/docs/content/docs/en/tools/google_vault.mdx b/apps/docs/content/docs/en/tools/google_vault.mdx index 7a7ece8cf3..d6e39c51ec 100644 --- a/apps/docs/content/docs/en/tools/google_vault.mdx +++ b/apps/docs/content/docs/en/tools/google_vault.mdx @@ -42,8 +42,8 @@ Create an export in a matter | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `matterId` | string | Yes | No description | -| `exportName` | string | Yes | No description | +| `matterId` | string | Yes | The matter ID | +| `exportName` | string | Yes | Name for the export \(avoid special characters\) | | `corpus` | string | Yes | Data corpus to export \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) | | `accountEmails` | string | No | Comma-separated list of user emails to scope export | | `orgUnitId` | string | No | Organization unit ID to scope export \(alternative to emails\) | @@ -65,10 +65,10 @@ List exports for a matter | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `matterId` | string | Yes | No description | -| `pageSize` | number | No | No description | -| `pageToken` | string | No | No description | -| `exportId` | string | No | No description | +| `matterId` | string | Yes | The matter ID | +| `pageSize` | number | No | Number of exports to return per page | +| `pageToken` | string | No | Token for pagination | +| `exportId` | string | No | Optional export ID to fetch a specific export | #### Output @@ -86,10 +86,10 @@ Download a single file from a Google Vault export (GCS object) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `matterId` | string | Yes | No description | -| `bucketName` | string | Yes | No description | -| `objectName` | string | Yes | No description | -| `fileName` | string | No | No description | +| `matterId` | string | Yes | The matter ID | +| `bucketName` | string | Yes | GCS bucket name from cloudStorageSink.files.bucketName | +| `objectName` | string | Yes | GCS object name from cloudStorageSink.files.objectName | +| `fileName` | string | No | Optional filename override for the downloaded file | #### Output @@ -105,8 +105,8 @@ Create a hold in a matter | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `matterId` | string | Yes | No description | -| `holdName` | string | Yes | No description | +| `matterId` | string | Yes | The matter ID | +| `holdName` | string | Yes | Name for the hold | | `corpus` | string | Yes | Data corpus to hold \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) | | `accountEmails` | string | No | Comma-separated list of user emails to put on hold | | `orgUnitId` | string | No | Organization unit ID to put on hold \(alternative to accounts\) | @@ -129,10 +129,10 @@ List holds for a matter | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `matterId` | string | Yes | No description | -| `pageSize` | number | No | No description | -| `pageToken` | string | No | No description | -| `holdId` | string | No | No description | +| `matterId` | string | Yes | The matter ID | +| `pageSize` | number | No | Number of holds to return per page | +| `pageToken` | string | No | Token for pagination | +| `holdId` | string | No | Optional hold ID to fetch a specific hold | #### Output @@ -150,8 +150,8 @@ Create a new matter in Google Vault | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `name` | string | Yes | No description | -| `description` | string | No | No description | +| `name` | string | Yes | Name for the new matter | +| `description` | string | No | Optional description for the matter | #### Output @@ -167,9 +167,9 @@ List matters, or get a specific matter if matterId is provided | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `pageSize` | number | No | No description | -| `pageToken` | string | No | No description | -| `matterId` | string | No | No description | +| `pageSize` | number | No | Number of matters to return per page | +| `pageToken` | string | No | Token for pagination | +| `matterId` | string | No | Optional matter ID to fetch a specific matter | #### Output diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index ecc5f9ff4c..25a6a9fb90 100644 --- a/apps/sim/blocks/blocks/google_vault.ts +++ b/apps/sim/blocks/blocks/google_vault.ts @@ -206,7 +206,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, // Date filtering for holds (only works with MAIL and GROUPS corpus) { - id: 'startTime', + id: 'holdStartTime', title: 'Start Time', type: 'short-input', placeholder: 'YYYY-MM-DDTHH:mm:ssZ', @@ -232,7 +232,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, }, { - id: 'endTime', + id: 'holdEndTime', title: 'End Time', type: 'short-input', placeholder: 'YYYY-MM-DDTHH:mm:ssZ', @@ -286,7 +286,7 @@ Return ONLY the search query - no explanations, no quotes, no extra text.`, }, // Search terms for holds (only works with MAIL and GROUPS corpus) { - id: 'terms', + id: 'holdTerms', title: 'Search Terms', type: 'long-input', placeholder: 'Enter search query (e.g., from:user@example.com subject:confidential)', @@ -300,7 +300,7 @@ Return ONLY the search query - no explanations, no quotes, no extra text.`, prompt: `Generate a Google Vault search query based on the user's description. The query can use Gmail-style search operators: - from:user@example.com - emails from specific sender -- to:user@example.com - emails to specific recipient +- to:user@example.com - emails to specific recipient - subject:keyword - emails with keyword in subject - has:attachment - emails with attachments - filename:pdf - emails with PDF attachments @@ -438,10 +438,14 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, } }, params: (params) => { - const { credential, ...rest } = params + const { credential, holdStartTime, holdEndTime, holdTerms, ...rest } = params return { ...rest, credential, + // Map hold-specific fields to their tool parameter names + ...(holdStartTime && { startTime: holdStartTime }), + ...(holdEndTime && { endTime: holdEndTime }), + ...(holdTerms && { terms: holdTerms }), } }, }, @@ -463,6 +467,18 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, // Create hold inputs holdName: { type: 'string', description: 'Name for the hold' }, + holdStartTime: { + type: 'string', + description: 'Start time for hold date filtering (ISO 8601 format, MAIL/GROUPS only)', + }, + holdEndTime: { + type: 'string', + description: 'End time for hold date filtering (ISO 8601 format, MAIL/GROUPS only)', + }, + holdTerms: { + type: 'string', + description: 'Search query terms for hold (MAIL/GROUPS only)', + }, includeSharedDrives: { type: 'boolean', description: 'Include files in shared drives (for DRIVE corpus holds)', @@ -484,12 +500,32 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, description: { type: 'string', description: 'Matter description' }, }, outputs: { - matters: { type: 'json', description: 'Array of matter objects (for list_matters)' }, - exports: { type: 'json', description: 'Array of export objects (for list_matters_export)' }, - holds: { type: 'json', description: 'Array of hold objects (for list_matters_holds)' }, - matter: { type: 'json', description: 'Created matter object (for create_matters)' }, - export: { type: 'json', description: 'Created export object (for create_matters_export)' }, - hold: { type: 'json', description: 'Created hold object (for create_matters_holds)' }, + matters: { + type: 'json', + description: 'Array of matter objects (for list_matters without matterId)', + }, + exports: { + type: 'json', + description: 'Array of export objects (for list_matters_export without exportId)', + }, + holds: { + type: 'json', + description: 'Array of hold objects (for list_matters_holds without holdId)', + }, + matter: { + type: 'json', + description: 'Single matter object (for create_matters or list_matters with matterId)', + }, + export: { + type: 'json', + description: + 'Single export object (for create_matters_export or list_matters_export with exportId)', + }, + hold: { + type: 'json', + description: + 'Single hold object (for create_matters_holds or list_matters_holds with holdId)', + }, file: { type: 'json', description: 'Downloaded export file (UserFile) from execution files' }, nextPageToken: { type: 'string', diff --git a/apps/sim/tools/google_vault/create_matters.ts b/apps/sim/tools/google_vault/create_matters.ts index 23aa90112b..b7535baf35 100644 --- a/apps/sim/tools/google_vault/create_matters.ts +++ b/apps/sim/tools/google_vault/create_matters.ts @@ -14,9 +14,24 @@ export const createMattersTool: ToolConfig = { }, params: { - accessToken: { type: 'string', required: true, visibility: 'hidden' }, - name: { type: 'string', required: true, visibility: 'user-only' }, - description: { type: 'string', required: false, visibility: 'user-only' }, + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Name for the new matter', + }, + description: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Optional description for the matter', + }, }, request: { diff --git a/apps/sim/tools/google_vault/create_matters_export.ts b/apps/sim/tools/google_vault/create_matters_export.ts index 2b93a6b586..e9e8ac0a2a 100644 --- a/apps/sim/tools/google_vault/create_matters_export.ts +++ b/apps/sim/tools/google_vault/create_matters_export.ts @@ -4,7 +4,7 @@ import type { ToolConfig } from '@/tools/types' export const createMattersExportTool: ToolConfig = { id: 'google_vault_create_matters_export', - name: 'Vault Create Export (by Matter)', + name: 'Vault Create Export', description: 'Create an export in a matter', version: '1.0', @@ -14,9 +14,24 @@ export const createMattersExportTool: ToolConfig = { id: 'google_vault_create_matters_holds', - name: 'Vault Create Hold (by Matter)', + name: 'Vault Create Hold', description: 'Create a hold in a matter', version: '1.0', @@ -14,9 +14,24 @@ export const createMattersHoldsTool: ToolConfig = { }, params: { - accessToken: { type: 'string', required: true, visibility: 'hidden' }, - pageSize: { type: 'number', required: false, visibility: 'user-only' }, - pageToken: { type: 'string', required: false, visibility: 'hidden' }, - matterId: { type: 'string', required: false, visibility: 'user-only' }, + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-only', + description: 'Number of matters to return per page', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Token for pagination', + }, + matterId: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Optional matter ID to fetch a specific matter', + }, }, request: { diff --git a/apps/sim/tools/google_vault/list_matters_export.ts b/apps/sim/tools/google_vault/list_matters_export.ts index 41429d2047..4ee064d007 100644 --- a/apps/sim/tools/google_vault/list_matters_export.ts +++ b/apps/sim/tools/google_vault/list_matters_export.ts @@ -4,7 +4,7 @@ import type { ToolConfig } from '@/tools/types' export const listMattersExportTool: ToolConfig = { id: 'google_vault_list_matters_export', - name: 'Vault List Exports (by Matter)', + name: 'Vault List Exports', description: 'List exports for a matter', version: '1.0', @@ -14,11 +14,36 @@ export const listMattersExportTool: ToolConfig = { id: 'google_vault_list_matters_holds', - name: 'Vault List Holds (by Matter)', + name: 'Vault List Holds', description: 'List holds for a matter', version: '1.0', @@ -14,11 +14,36 @@ export const listMattersHoldsTool: ToolConfig }, params: { - accessToken: { type: 'string', required: true, visibility: 'hidden' }, - matterId: { type: 'string', required: true, visibility: 'user-only' }, - pageSize: { type: 'number', required: false, visibility: 'user-only' }, - pageToken: { type: 'string', required: false, visibility: 'hidden' }, - holdId: { type: 'string', required: false, visibility: 'user-only' }, + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + matterId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'The matter ID', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-only', + description: 'Number of holds to return per page', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Token for pagination', + }, + holdId: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Optional hold ID to fetch a specific hold', + }, }, request: {