diff --git a/apps/docs/content/docs/en/tools/google_vault.mdx b/apps/docs/content/docs/en/tools/google_vault.mdx index 39714d1122..7f5d3b34d5 100644 --- a/apps/docs/content/docs/en/tools/google_vault.mdx +++ b/apps/docs/content/docs/en/tools/google_vault.mdx @@ -31,6 +31,9 @@ Create an export in a matter | `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 @@ -91,6 +94,10 @@ Create a hold in a matter | `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 diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index c54098aad3..ecc5f9ff4c 100644 --- a/apps/sim/blocks/blocks/google_vault.ts +++ b/apps/sim/blocks/blocks/google_vault.ts @@ -159,6 +159,167 @@ 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 (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' }, + 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. +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_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. +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', + }, + }, + // 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' }, + 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 + +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...', + }, + }, + // 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 +457,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/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 f468fc7ab7..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)', @@ -36,6 +35,24 @@ export const createMattersExportTool: ToolConfig { - // Handle accountEmails - can be string (comma-separated) or array let emails: string[] = [] if (params.accountEmails) { if (Array.isArray(params.accountEmails)) { @@ -75,7 +91,6 @@ 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 f3b7a1e536..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,6 +35,30 @@ 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)) { @@ -66,12 +87,29 @@ 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 } } + if (params.corpus === 'MAIL' || params.corpus === 'GROUPS') { + const hasQueryParams = params.terms || params.startTime || params.endTime + if (hasQueryParams) { + const queryObj: any = {} + if (params.terms) queryObj.terms = params.terms + if (params.startTime) queryObj.startTime = params.startTime + if (params.endTime) queryObj.endTime = params.endTime + + if (params.corpus === 'MAIL') { + body.query = { mailQuery: queryObj } + } else { + body.query = { groupsQuery: queryObj } + } + } + } else if (params.corpus === 'DRIVE' && params.includeSharedDrives) { + body.query = { driveQuery: { includeSharedDriveFiles: params.includeSharedDrives } } + } + return body }, }, @@ -79,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 dff9aa42fa..a94ff39819 100644 --- a/apps/sim/tools/google_vault/types.ts +++ b/apps/sim/tools/google_vault/types.ts @@ -5,31 +5,48 @@ 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 endTime?: string - timeZone?: string includeSharedDrives?: boolean } 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' @@ -37,14 +54,18 @@ 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 + terms?: string + startTime?: string + endTime?: string + includeSharedDrives?: boolean } 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 +}