diff --git a/apps/docs/content/docs/en/tools/google_vault.mdx b/apps/docs/content/docs/en/tools/google_vault.mdx index 1dde5f9e48..d6e39c51ec 100644 --- a/apps/docs/content/docs/en/tools/google_vault.mdx +++ b/apps/docs/content/docs/en/tools/google_vault.mdx @@ -36,43 +36,47 @@ 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 | | --------- | ---- | -------- | ----------- | +| `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\) | +| `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 | 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 | 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 | 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` @@ -82,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 @@ -95,82 +99,84 @@ 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 | | --------- | ---- | -------- | ----------- | +| `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\) | +| `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 | 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 | 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 | 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 | Name for the new matter | +| `description` | string | No | Optional description for the matter | #### 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 | 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 | 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/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts index c54098aad3..25a6a9fb90 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: 'holdStartTime', + 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: 'holdEndTime', + 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: 'holdTerms', + 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', @@ -277,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 }), } }, }, @@ -296,9 +461,28 @@ 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' }, + 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)', + }, // Download export file inputs bucketName: { type: 'string', description: 'GCS bucket name from export' }, @@ -316,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/executor/handlers/index.ts b/apps/sim/executor/handlers/index.ts new file mode 100644 index 0000000000..fb27421b2d --- /dev/null +++ b/apps/sim/executor/handlers/index.ts @@ -0,0 +1,29 @@ +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 { 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, + ResponseBlockHandler, + HumanInTheLoopBlockHandler, + RouterBlockHandler, + TriggerBlockHandler, + VariablesBlockHandler, + WaitBlockHandler, + WorkflowBlockHandler, +} diff --git a/apps/sim/tools/google_vault/create_matters.ts b/apps/sim/tools/google_vault/create_matters.ts index 9886606656..b7535baf35 100644 --- a/apps/sim/tools/google_vault/create_matters.ts +++ b/apps/sim/tools/google_vault/create_matters.ts @@ -1,15 +1,9 @@ +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', + id: 'google_vault_create_matters', name: 'Vault Create Matter', description: 'Create a new matter in Google Vault', version: '1.0', @@ -20,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: { @@ -38,7 +47,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..e9e8ac0a2a 100644 --- a/apps/sim/tools/google_vault/create_matters_export.ts +++ b/apps/sim/tools/google_vault/create_matters_export.ts @@ -1,11 +1,10 @@ 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)', + id: 'google_vault_create_matters_export', + name: 'Vault Create Export', description: 'Create an export in a matter', version: '1.0', @@ -15,9 +14,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 +106,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..5a3b2d9ebf 100644 --- a/apps/sim/tools/google_vault/create_matters_holds.ts +++ b/apps/sim/tools/google_vault/create_matters_holds.ts @@ -1,11 +1,10 @@ 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)', + id: 'google_vault_create_matters_holds', + name: 'Vault Create Hold', description: 'Create a hold in a matter', version: '1.0', @@ -15,9 +14,24 @@ 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 +102,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 +132,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..69f83ed578 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)', @@ -23,28 +14,51 @@ export const downloadExportFileTool: ToolConfig = { }, params: { - accessToken: { type: 'string', required: true, visibility: 'hidden' }, - matterId: { type: 'string', required: true, visibility: 'user-only' }, - bucketName: { type: 'string', required: true, visibility: 'user-only' }, - objectName: { type: 'string', required: true, visibility: 'user-only' }, - fileName: { 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', + }, + bucketName: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GCS bucket name from cloudStorageSink.files.bucketName', + }, + objectName: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GCS object name from cloudStorageSink.files.objectName', + }, + fileName: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Optional filename override for the downloaded file', + }, }, request: { 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 +71,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 +84,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 +93,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 +119,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..1ffced1b73 100644 --- a/apps/sim/tools/google_vault/list_matters.ts +++ b/apps/sim/tools/google_vault/list_matters.ts @@ -1,14 +1,9 @@ +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', + id: 'google_vault_list_matters', name: 'Vault List Matters', description: 'List matters, or get a specific matter if matterId is provided', version: '1.0', @@ -19,10 +14,30 @@ export const listMattersTool: 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: { @@ -47,7 +62,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..4ee064d007 100644 --- a/apps/sim/tools/google_vault/list_matters_export.ts +++ b/apps/sim/tools/google_vault/list_matters_export.ts @@ -1,9 +1,10 @@ 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 = { - id: 'list_matters_export', - name: 'Vault List Exports (by Matter)', + id: 'google_vault_list_matters_export', + name: 'Vault List Exports', description: 'List exports for a matter', version: '1.0', @@ -13,11 +14,36 @@ 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..95eb1cd463 100644 --- a/apps/sim/tools/google_vault/list_matters_holds.ts +++ b/apps/sim/tools/google_vault/list_matters_holds.ts @@ -1,9 +1,10 @@ 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 = { - id: 'list_matters_holds', - name: 'Vault List Holds (by Matter)', + id: 'google_vault_list_matters_holds', + name: 'Vault List Holds', description: 'List holds for a matter', version: '1.0', @@ -13,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: { @@ -42,7 +68,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 +}