mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Feat(google vault): added google vault tool (#1459)
* first push pre testing * toosl working * progress * bun run lint * added doc * changed google client ID and secret back * cleaned up oauth * removed comment * removed any and added manual content --------- Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net>
This commit is contained in:
177
apps/docs/content/docs/en/tools/google_vault.mdx
Normal file
177
apps/docs/content/docs/en/tools/google_vault.mdx
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: Google Vault
|
||||
description: Search, export, and manage holds/exports for Vault matters
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_vault"
|
||||
color="#E8F0FE"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 82 82'>
|
||||
<path
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
d='M58.0251 41.1399L63.8516 36.7242L69.0032 30.157C67.372 26.032 63.097 18.4149 63.097 18.4149C63.097 18.4149 55.6204 14.618 51.9923 13.193L45.3454 17.918L41.0563 24.0492L36.7626 17.8336L30.2001 13.1133C26.5391 14.5195 19.0063 18.7805 19.0063 18.7805C19.0063 18.7805 14.6423 25.9852 12.9923 30.157L18.1532 36.7336L23.9048 41.0742L17.7969 45.7289L12.8798 52.0195C14.3329 55.807 18.3501 63.0352 18.3501 63.0352C18.3501 63.0352 25.8735 67.6289 29.9188 69.232L36.1016 64.4649L41.0001 58.1649L45.3454 63.9258L52.072 69.2367C55.8595 67.7414 63.0595 63.6305 63.0595 63.6305C63.0595 63.6305 67.4798 56.1961 69.036 52.2258L63.9782 45.7102L58.0251 41.1399ZM41.0048 53.4633C34.2501 53.4633 28.7704 47.9883 28.7704 41.2289C28.7704 34.4742 34.2454 28.9945 41.0048 28.9945C47.7641 28.9945 53.2391 34.4695 53.2391 41.2289C53.2391 47.9883 47.7595 53.4633 41.0048 53.4633Z'
|
||||
fill='#1967D2'
|
||||
/>
|
||||
<path
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
d='M58.025 41.1401L69.0078 30.1573C70.3672 33.5839 71.1172 37.3198 71.1172 41.2292C71.1172 45.1104 70.3766 48.8183 69.036 52.2261L58.025 41.1401ZM12.9969 30.162C11.6422 33.5886 10.8875 37.3198 10.8875 41.2292C10.8875 45.0354 11.6 48.6683 12.8891 52.0198L23.9094 41.0745L12.9969 30.162ZM51.9969 13.1933C48.5891 11.8573 44.886 11.1167 41.0047 11.1167C37.1985 11.1167 33.561 11.8292 30.2047 13.1183L41.061 24.0495L51.9969 13.1933ZM29.9328 69.2323C33.3594 70.5917 37.0953 71.3417 41.0047 71.3417C44.9141 71.3417 48.6453 70.587 52.0719 69.2323L41 58.1651L29.9328 69.2323ZM69.036 52.2261C65.9891 59.9839 59.8203 66.1667 52.0719 69.2323L62.811 79.9714C64.2828 81.4433 66.6641 81.4433 68.136 79.9714L79.761 68.3464C81.2282 66.8792 81.2328 64.5026 79.7703 63.0308L69.036 52.2261ZM69.0078 30.1573L79.9766 19.1886C81.4485 17.7167 81.4485 15.3354 79.9766 13.8636L68.3516 2.23857C66.8844 0.771387 64.5078 0.766699 63.036 2.2292L51.9922 13.1933C59.7547 16.2401 65.9422 22.4089 69.0078 30.1573ZM12.8891 52.0198L2.03284 62.8011C0.551587 64.2683 0.551587 66.6589 2.02346 68.1354L13.8641 79.9761C15.336 81.4479 17.7172 81.4479 19.1891 79.9761L29.9328 69.2323C22.1141 66.1386 15.9078 59.8761 12.8891 52.0198ZM30.2047 13.1183L19.1985 2.03232C17.7313 0.551074 15.3406 0.551074 13.8641 2.02295L2.02346 13.8636C0.551587 15.3354 0.551587 17.7167 2.02346 19.1886L12.9969 30.162C16.0906 22.3479 22.3532 16.137 30.2047 13.1183Z'
|
||||
fill='#4285F4'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Connect Google Vault to create exports, list exports, and manage holds within matters.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `google_vault_create_matters_export`
|
||||
|
||||
Create an export in a matter
|
||||
|
||||
#### Input
|
||||
|
||||
| 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\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
### `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 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
### `google_vault_download_export_file`
|
||||
|
||||
Download a single file from a Google Vault export (GCS object)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `matterId` | string | Yes | No description |
|
||||
| `bucketName` | string | Yes | No description |
|
||||
| `objectName` | string | Yes | No description |
|
||||
| `fileName` | string | No | No description |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `file` | file | Downloaded Vault export file stored in execution files |
|
||||
|
||||
### `google_vault_create_matters_holds`
|
||||
|
||||
Create a hold in a matter
|
||||
|
||||
#### Input
|
||||
|
||||
| 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\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
### `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 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
### `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 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
### `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 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `output` | json | Vault API response data |
|
||||
| `file` | json | Downloaded export file \(UserFile\) from execution files |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `google_vault`
|
||||
@@ -1,7 +1,9 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { generateInternalToken } from '@/lib/auth/internal'
|
||||
import { isDev } from '@/lib/environment'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { validateProxyUrl } from '@/lib/security/url-validation'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
import { executeTool } from '@/tools'
|
||||
import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils'
|
||||
@@ -77,6 +79,79 @@ export async function GET(request: Request) {
|
||||
const targetUrl = url.searchParams.get('url')
|
||||
const requestId = generateRequestId()
|
||||
|
||||
// Vault download proxy: /api/proxy?vaultDownload=1&bucket=...&object=...&credentialId=...
|
||||
const vaultDownload = url.searchParams.get('vaultDownload')
|
||||
if (vaultDownload === '1') {
|
||||
try {
|
||||
const bucket = url.searchParams.get('bucket')
|
||||
const objectParam = url.searchParams.get('object')
|
||||
const credentialId = url.searchParams.get('credentialId')
|
||||
|
||||
if (!bucket || !objectParam || !credentialId) {
|
||||
return createErrorResponse('Missing bucket, object, or credentialId', 400)
|
||||
}
|
||||
|
||||
// Fetch access token using existing token API
|
||||
const baseUrl = new URL(getBaseUrl())
|
||||
const tokenUrl = new URL('/api/auth/oauth/token', baseUrl)
|
||||
|
||||
// Build headers: forward session cookies if present; include internal auth for server-side
|
||||
const tokenHeaders: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||
const incomingCookie = request.headers.get('cookie')
|
||||
if (incomingCookie) tokenHeaders.Cookie = incomingCookie
|
||||
try {
|
||||
const internalToken = await generateInternalToken()
|
||||
tokenHeaders.Authorization = `Bearer ${internalToken}`
|
||||
} catch (_e) {
|
||||
// best-effort internal auth
|
||||
}
|
||||
|
||||
// Optional workflow context for collaboration auth
|
||||
const workflowId = url.searchParams.get('workflowId') || undefined
|
||||
|
||||
const tokenRes = await fetch(tokenUrl.toString(), {
|
||||
method: 'POST',
|
||||
headers: tokenHeaders,
|
||||
body: JSON.stringify({ credentialId, workflowId }),
|
||||
})
|
||||
|
||||
if (!tokenRes.ok) {
|
||||
const err = await tokenRes.text()
|
||||
return createErrorResponse(`Failed to fetch access token: ${err}`, 401)
|
||||
}
|
||||
|
||||
const tokenJson = await tokenRes.json()
|
||||
const accessToken = tokenJson.accessToken
|
||||
if (!accessToken) {
|
||||
return createErrorResponse('No access token available', 401)
|
||||
}
|
||||
|
||||
// Avoid double-encoding: incoming object may already be percent-encoded
|
||||
const objectDecoded = decodeURIComponent(objectParam)
|
||||
const gcsUrl = `https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(
|
||||
bucket
|
||||
)}/o/${encodeURIComponent(objectDecoded)}?alt=media`
|
||||
|
||||
const fileRes = await fetch(gcsUrl, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
|
||||
if (!fileRes.ok) {
|
||||
const errText = await fileRes.text()
|
||||
return createErrorResponse(errText || 'Failed to download file', fileRes.status)
|
||||
}
|
||||
|
||||
const headers = new Headers()
|
||||
fileRes.headers.forEach((v, k) => headers.set(k, v))
|
||||
return new NextResponse(fileRes.body, { status: 200, headers })
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Vault download proxy failed`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return createErrorResponse('Vault download failed', 500)
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetUrl) {
|
||||
logger.error(`[${requestId}] Missing 'url' parameter`)
|
||||
return createErrorResponse("Missing 'url' parameter", 400)
|
||||
|
||||
284
apps/sim/blocks/blocks/google_vault.ts
Normal file
284
apps/sim/blocks/blocks/google_vault.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
import { GoogleVaultIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
|
||||
export const GoogleVaultBlock: BlockConfig = {
|
||||
type: 'google_vault',
|
||||
name: 'Google Vault',
|
||||
description: 'Search, export, and manage holds/exports for Vault matters',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Connect Google Vault to create exports, list exports, and manage holds within matters.',
|
||||
docsLink: 'https://developers.google.com/vault',
|
||||
category: 'tools',
|
||||
bgColor: '#E8F0FE',
|
||||
icon: GoogleVaultIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Create Export', id: 'create_matters_export' },
|
||||
{ label: 'List Exports', id: 'list_matters_export' },
|
||||
{ label: 'Download Export File', id: 'download_export_file' },
|
||||
{ label: 'Create Hold', id: 'create_matters_holds' },
|
||||
{ label: 'List Holds', id: 'list_matters_holds' },
|
||||
{ label: 'Create Matter', id: 'create_matters' },
|
||||
{ label: 'List Matters', id: 'list_matters' },
|
||||
],
|
||||
value: () => 'list_matters_export',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Google Vault Account',
|
||||
type: 'oauth-input',
|
||||
layout: 'full',
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
serviceId: 'google-vault',
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/ediscovery',
|
||||
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||
],
|
||||
placeholder: 'Select Google Vault account',
|
||||
},
|
||||
// Create Hold inputs
|
||||
{
|
||||
id: 'matterId',
|
||||
title: 'Matter ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Matter ID',
|
||||
condition: () => ({
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_matters_export',
|
||||
'list_matters_export',
|
||||
'download_export_file',
|
||||
'create_matters_holds',
|
||||
'list_matters_holds',
|
||||
],
|
||||
}),
|
||||
},
|
||||
// Download Export File inputs
|
||||
{
|
||||
id: 'bucketName',
|
||||
title: 'Bucket Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Vault export bucket (from cloudStorageSink.files.bucketName)',
|
||||
condition: { field: 'operation', value: 'download_export_file' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'objectName',
|
||||
title: 'Object Name',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Vault export object (from cloudStorageSink.files.objectName)',
|
||||
condition: { field: 'operation', value: 'download_export_file' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'fileName',
|
||||
title: 'File Name (optional)',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Override filename used for storage/display',
|
||||
condition: { field: 'operation', value: 'download_export_file' },
|
||||
},
|
||||
{
|
||||
id: 'exportName',
|
||||
title: 'Export Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Name for the export',
|
||||
condition: { field: 'operation', value: 'create_matters_export' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'holdName',
|
||||
title: 'Hold Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Name of the hold',
|
||||
condition: { field: 'operation', value: 'create_matters_holds' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'corpus',
|
||||
title: 'Corpus',
|
||||
type: 'dropdown',
|
||||
layout: 'half',
|
||||
options: [
|
||||
{ id: 'MAIL', label: 'MAIL' },
|
||||
{ id: 'DRIVE', label: 'DRIVE' },
|
||||
{ id: 'GROUPS', label: 'GROUPS' },
|
||||
{ id: 'HANGOUTS_CHAT', label: 'HANGOUTS_CHAT' },
|
||||
{ id: 'VOICE', label: 'VOICE' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_matters_holds', 'create_matters_export'] },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'accountEmails',
|
||||
title: 'Account Emails',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Comma-separated emails (alternative to Org Unit)',
|
||||
condition: { field: 'operation', value: ['create_matters_holds', 'create_matters_export'] },
|
||||
},
|
||||
{
|
||||
id: 'orgUnitId',
|
||||
title: 'Org Unit ID',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Org Unit ID (alternative to emails)',
|
||||
condition: { field: 'operation', value: ['create_matters_holds', 'create_matters_export'] },
|
||||
},
|
||||
{
|
||||
id: 'exportId',
|
||||
title: 'Export ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Export ID (optional to fetch a specific export)',
|
||||
condition: { field: 'operation', value: 'list_matters_export' },
|
||||
},
|
||||
{
|
||||
id: 'holdId',
|
||||
title: 'Hold ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Hold ID (optional to fetch a specific hold)',
|
||||
condition: { field: 'operation', value: 'list_matters_holds' },
|
||||
},
|
||||
{
|
||||
id: 'pageSize',
|
||||
title: 'Page Size',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Number of items to return',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_matters_export', 'list_matters_holds', 'list_matters'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pageToken',
|
||||
title: 'Page Token',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Pagination token',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_matters_export', 'list_matters_holds', 'list_matters'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: 'name',
|
||||
title: 'Matter Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Matter name',
|
||||
condition: { field: 'operation', value: 'create_matters' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
title: 'Description',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Optional description for the matter',
|
||||
condition: { field: 'operation', value: 'create_matters' },
|
||||
},
|
||||
// Optional get specific matter by ID
|
||||
{
|
||||
id: 'matterId',
|
||||
title: 'Matter ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Matter ID (optional to fetch a specific matter)',
|
||||
condition: { field: 'operation', value: 'list_matters' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'google_vault_create_matters_export',
|
||||
'google_vault_list_matters_export',
|
||||
'google_vault_download_export_file',
|
||||
'google_vault_create_matters_holds',
|
||||
'google_vault_list_matters_holds',
|
||||
'google_vault_create_matters',
|
||||
'google_vault_list_matters',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'create_matters_export':
|
||||
return 'google_vault_create_matters_export'
|
||||
case 'list_matters_export':
|
||||
return 'google_vault_list_matters_export'
|
||||
case 'download_export_file':
|
||||
return 'google_vault_download_export_file'
|
||||
case 'create_matters_holds':
|
||||
return 'google_vault_create_matters_holds'
|
||||
case 'list_matters_holds':
|
||||
return 'google_vault_list_matters_holds'
|
||||
case 'create_matters':
|
||||
return 'google_vault_create_matters'
|
||||
case 'list_matters':
|
||||
return 'google_vault_list_matters'
|
||||
default:
|
||||
throw new Error(`Invalid Google Vault operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, ...rest } = params
|
||||
return {
|
||||
...rest,
|
||||
credential,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
// Core inputs
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Google Vault OAuth credential' },
|
||||
matterId: { type: 'string', description: 'Matter ID' },
|
||||
|
||||
// Create export inputs
|
||||
exportName: { type: 'string', description: 'Name for the export' },
|
||||
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' },
|
||||
|
||||
// Create hold inputs
|
||||
holdName: { type: 'string', description: 'Name for the hold' },
|
||||
|
||||
// Download export file inputs
|
||||
bucketName: { type: 'string', description: 'GCS bucket name from export' },
|
||||
objectName: { type: 'string', description: 'GCS object name from export' },
|
||||
fileName: { type: 'string', description: 'Optional filename override' },
|
||||
|
||||
// List operations inputs
|
||||
exportId: { type: 'string', description: 'Specific export ID to fetch' },
|
||||
holdId: { type: 'string', description: 'Specific hold ID to fetch' },
|
||||
pageSize: { type: 'number', description: 'Number of items per page' },
|
||||
pageToken: { type: 'string', description: 'Pagination token' },
|
||||
|
||||
// Create matter inputs
|
||||
name: { type: 'string', description: 'Matter name' },
|
||||
description: { type: 'string', description: 'Matter description' },
|
||||
},
|
||||
outputs: {
|
||||
// Common outputs
|
||||
output: { type: 'json', description: 'Vault API response data' },
|
||||
// Download export file output
|
||||
file: { type: 'json', description: 'Downloaded export file (UserFile) from execution files' },
|
||||
},
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
|
||||
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
|
||||
import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
|
||||
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
|
||||
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
|
||||
import { HuggingFaceBlock } from '@/blocks/blocks/huggingface'
|
||||
import { HunterBlock } from '@/blocks/blocks/hunter'
|
||||
import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator'
|
||||
@@ -113,6 +114,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
google_forms: GoogleFormsBlock,
|
||||
google_search: GoogleSearchBlock,
|
||||
google_sheets: GoogleSheetsBlock,
|
||||
google_vault: GoogleVaultBlock,
|
||||
huggingface: HuggingFaceBlock,
|
||||
hunter: HunterBlock,
|
||||
image_generator: ImageGeneratorBlock,
|
||||
|
||||
@@ -3711,3 +3711,18 @@ export const ResendIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const GoogleVaultIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 82 82'>
|
||||
<path
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
d='M58.0251 41.1399L63.8516 36.7242L69.0032 30.157C67.372 26.032 63.097 18.4149 63.097 18.4149C63.097 18.4149 55.6204 14.618 51.9923 13.193L45.3454 17.918L41.0563 24.0492L36.7626 17.8336L30.2001 13.1133C26.5391 14.5195 19.0063 18.7805 19.0063 18.7805C19.0063 18.7805 14.6423 25.9852 12.9923 30.157L18.1532 36.7336L23.9048 41.0742L17.7969 45.7289L12.8798 52.0195C14.3329 55.807 18.3501 63.0352 18.3501 63.0352C18.3501 63.0352 25.8735 67.6289 29.9188 69.232L36.1016 64.4649L41.0001 58.1649L45.3454 63.9258L52.072 69.2367C55.8595 67.7414 63.0595 63.6305 63.0595 63.6305C63.0595 63.6305 67.4798 56.1961 69.036 52.2258L63.9782 45.7102L58.0251 41.1399ZM41.0048 53.4633C34.2501 53.4633 28.7704 47.9883 28.7704 41.2289C28.7704 34.4742 34.2454 28.9945 41.0048 28.9945C47.7641 28.9945 53.2391 34.4695 53.2391 41.2289C53.2391 47.9883 47.7595 53.4633 41.0048 53.4633Z'
|
||||
fill='#1967D2'
|
||||
/>
|
||||
<path
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
d='M58.025 41.1401L69.0078 30.1573C70.3672 33.5839 71.1172 37.3198 71.1172 41.2292C71.1172 45.1104 70.3766 48.8183 69.036 52.2261L58.025 41.1401ZM12.9969 30.162C11.6422 33.5886 10.8875 37.3198 10.8875 41.2292C10.8875 45.0354 11.6 48.6683 12.8891 52.0198L23.9094 41.0745L12.9969 30.162ZM51.9969 13.1933C48.5891 11.8573 44.886 11.1167 41.0047 11.1167C37.1985 11.1167 33.561 11.8292 30.2047 13.1183L41.061 24.0495L51.9969 13.1933ZM29.9328 69.2323C33.3594 70.5917 37.0953 71.3417 41.0047 71.3417C44.9141 71.3417 48.6453 70.587 52.0719 69.2323L41 58.1651L29.9328 69.2323ZM69.036 52.2261C65.9891 59.9839 59.8203 66.1667 52.0719 69.2323L62.811 79.9714C64.2828 81.4433 66.6641 81.4433 68.136 79.9714L79.761 68.3464C81.2282 66.8792 81.2328 64.5026 79.7703 63.0308L69.036 52.2261ZM69.0078 30.1573L79.9766 19.1886C81.4485 17.7167 81.4485 15.3354 79.9766 13.8636L68.3516 2.23857C66.8844 0.771387 64.5078 0.766699 63.036 2.2292L51.9922 13.1933C59.7547 16.2401 65.9422 22.4089 69.0078 30.1573ZM12.8891 52.0198L2.03284 62.8011C0.551587 64.2683 0.551587 66.6589 2.02346 68.1354L13.8641 79.9761C15.336 81.4479 17.7172 81.4479 19.1891 79.9761L29.9328 69.2323C22.1141 66.1386 15.9078 59.8761 12.8891 52.0198ZM30.2047 13.1183L19.1985 2.03232C17.7313 0.551074 15.3406 0.551074 13.8641 2.02295L2.02346 13.8636C0.551587 15.3354 0.551587 17.7167 2.02346 19.1886L12.9969 30.162C16.0906 22.3479 22.3532 16.137 30.2047 13.1183Z'
|
||||
fill='#4285F4'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -469,6 +469,22 @@ export const auth = betterAuth({
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-forms`,
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'google-vault',
|
||||
clientId: env.GOOGLE_CLIENT_ID as string,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET as string,
|
||||
discoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration',
|
||||
accessType: 'offline',
|
||||
scopes: [
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
'https://www.googleapis.com/auth/ediscovery',
|
||||
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||
],
|
||||
prompt: 'consent',
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/google-vault`,
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'microsoft-teams',
|
||||
clientId: env.MICROSOFT_CLIENT_ID as string,
|
||||
|
||||
@@ -56,6 +56,7 @@ export type OAuthService =
|
||||
| 'google-docs'
|
||||
| 'google-sheets'
|
||||
| 'google-calendar'
|
||||
| 'google-vault'
|
||||
| 'google-forms'
|
||||
| 'github'
|
||||
| 'x'
|
||||
@@ -162,6 +163,18 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
baseProviderIcon: (props) => GoogleIcon(props),
|
||||
scopes: ['https://www.googleapis.com/auth/calendar'],
|
||||
},
|
||||
'google-vault': {
|
||||
id: 'google-vault',
|
||||
name: 'Google Vault',
|
||||
description: 'Search, export, and manage matters/holds via Google Vault.',
|
||||
providerId: 'google-vault',
|
||||
icon: (props) => GoogleIcon(props),
|
||||
baseProviderIcon: (props) => GoogleIcon(props),
|
||||
scopes: [
|
||||
'https://www.googleapis.com/auth/ediscovery',
|
||||
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'gmail',
|
||||
},
|
||||
@@ -534,6 +547,9 @@ export function getServiceIdFromScopes(provider: OAuthProvider, scopes: string[]
|
||||
if (scopes.some((scope) => scope.includes('forms'))) {
|
||||
return 'google-forms'
|
||||
}
|
||||
if (scopes.some((scope) => scope.includes('ediscovery'))) {
|
||||
return 'google-vault'
|
||||
}
|
||||
} else if (provider === 'microsoft-teams') {
|
||||
return 'microsoft-teams'
|
||||
} else if (provider === 'outlook') {
|
||||
@@ -947,7 +963,6 @@ export async function refreshOAuthToken(
|
||||
status: response.status,
|
||||
error: errorText,
|
||||
parsedError: errorData,
|
||||
provider,
|
||||
providerId,
|
||||
})
|
||||
throw new Error(`Failed to refresh token: ${response.status} ${errorText}`)
|
||||
|
||||
46
apps/sim/tools/google_vault/create_matters.ts
Normal file
46
apps/sim/tools/google_vault/create_matters.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
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<GoogleVaultCreateMattersParams> = {
|
||||
id: 'create_matters',
|
||||
name: 'Vault Create Matter',
|
||||
description: 'Create a new matter in Google Vault',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
name: { type: 'string', required: true, visibility: 'user-only' },
|
||||
description: { type: 'string', required: false, visibility: 'user-only' },
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => `https://vault.googleapis.com/v1/matters`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({ name: params.name, description: params.description }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to create matter')
|
||||
}
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
97
apps/sim/tools/google_vault/create_matters_export.ts
Normal file
97
apps/sim/tools/google_vault/create_matters_export.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { GoogleVaultCreateMattersExportParams } from '@/tools/google_vault/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
// matters.exports.create
|
||||
// POST https://vault.googleapis.com/v1/matters/{matterId}/exports
|
||||
export const createMattersExportTool: ToolConfig<GoogleVaultCreateMattersExportParams> = {
|
||||
id: 'create_matters_export',
|
||||
name: 'Vault Create Export (by Matter)',
|
||||
description: 'Create an export in a matter',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
matterId: { type: 'string', required: true, visibility: 'user-only' },
|
||||
exportName: { type: 'string', required: true, visibility: 'user-only' },
|
||||
corpus: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Data corpus to export (MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE)',
|
||||
},
|
||||
accountEmails: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of user emails to scope export',
|
||||
},
|
||||
orgUnitId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Organization unit ID to scope export (alternative to emails)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://vault.googleapis.com/v1/matters/${params.matterId}/exports`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
// Handle accountEmails - can be string (comma-separated) or array
|
||||
let emails: string[] = []
|
||||
if (params.accountEmails) {
|
||||
if (Array.isArray(params.accountEmails)) {
|
||||
emails = params.accountEmails
|
||||
} else if (typeof params.accountEmails === 'string') {
|
||||
emails = params.accountEmails
|
||||
.split(',')
|
||||
.map((e) => e.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
const scope =
|
||||
emails.length > 0
|
||||
? { accountInfo: { emails } }
|
||||
: params.orgUnitId
|
||||
? { orgUnitInfo: { orgUnitId: params.orgUnitId } }
|
||||
: {}
|
||||
|
||||
const searchMethod = emails.length > 0 ? 'ACCOUNT' : params.orgUnitId ? 'ORG_UNIT' : undefined
|
||||
|
||||
const query: any = {
|
||||
corpus: params.corpus,
|
||||
dataScope: 'ALL_DATA',
|
||||
searchMethod: searchMethod,
|
||||
terms: params.terms || undefined,
|
||||
startTime: params.startTime || undefined,
|
||||
endTime: params.endTime || undefined,
|
||||
timeZone: params.timeZone || undefined,
|
||||
...scope,
|
||||
}
|
||||
|
||||
return {
|
||||
name: params.exportName,
|
||||
query,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to create export')
|
||||
}
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
87
apps/sim/tools/google_vault/create_matters_holds.ts
Normal file
87
apps/sim/tools/google_vault/create_matters_holds.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { GoogleVaultCreateMattersHoldsParams } from '@/tools/google_vault/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
// matters.holds.create
|
||||
// POST https://vault.googleapis.com/v1/matters/{matterId}/holds
|
||||
export const createMattersHoldsTool: ToolConfig<GoogleVaultCreateMattersHoldsParams> = {
|
||||
id: 'create_matters_holds',
|
||||
name: 'Vault Create Hold (by Matter)',
|
||||
description: 'Create a hold in a matter',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: { type: 'string', required: true, visibility: 'hidden' },
|
||||
matterId: { type: 'string', required: true, visibility: 'user-only' },
|
||||
holdName: { type: 'string', required: true, visibility: 'user-only' },
|
||||
corpus: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Data corpus to hold (MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE)',
|
||||
},
|
||||
accountEmails: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comma-separated list of user emails to put on hold',
|
||||
},
|
||||
orgUnitId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Organization unit ID to put on hold (alternative to accounts)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://vault.googleapis.com/v1/matters/${params.matterId}/holds`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
// 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)) {
|
||||
emails = params.accountEmails
|
||||
} else if (typeof params.accountEmails === 'string') {
|
||||
emails = params.accountEmails
|
||||
.split(',')
|
||||
.map((e) => e.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
if (emails.length > 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 }
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to create hold')
|
||||
}
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
132
apps/sim/tools/google_vault/download_export_file.ts
Normal file
132
apps/sim/tools/google_vault/download_export_file.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
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<DownloadParams> = {
|
||||
id: 'google_vault_download_export_file',
|
||||
name: 'Vault Download Export File',
|
||||
description: 'Download a single file from a Google Vault export (GCS object)',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: [
|
||||
'https://www.googleapis.com/auth/ediscovery',
|
||||
// Required to fetch the object bytes from the Cloud Storage bucket that Vault uses
|
||||
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||
],
|
||||
},
|
||||
|
||||
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' },
|
||||
},
|
||||
|
||||
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) => {
|
||||
if (!response.ok) {
|
||||
let details: any
|
||||
try {
|
||||
details = await response.json()
|
||||
} catch {
|
||||
try {
|
||||
const text = await response.text()
|
||||
details = { error: text }
|
||||
} catch {
|
||||
details = undefined
|
||||
}
|
||||
}
|
||||
throw new Error(details?.error || `Failed to download Vault export file (${response.status})`)
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
|
||||
const bucket = encodeURIComponent(params.bucketName)
|
||||
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: {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!downloadResponse.ok) {
|
||||
const errorText = await downloadResponse.text().catch(() => '')
|
||||
throw new Error(`Failed to download file: ${errorText || downloadResponse.statusText}`)
|
||||
}
|
||||
|
||||
const contentType = downloadResponse.headers.get('content-type') || 'application/octet-stream'
|
||||
const disposition = downloadResponse.headers.get('content-disposition') || ''
|
||||
const match = disposition.match(/filename\*=UTF-8''([^;]+)|filename="([^"]+)"/)
|
||||
|
||||
let resolvedName = params.fileName
|
||||
if (!resolvedName) {
|
||||
if (match?.[1]) {
|
||||
try {
|
||||
resolvedName = decodeURIComponent(match[1])
|
||||
} catch {
|
||||
resolvedName = match[1]
|
||||
}
|
||||
} else if (match?.[2]) {
|
||||
resolvedName = match[2]
|
||||
} else if (params.objectName) {
|
||||
const parts = params.objectName.split('/')
|
||||
resolvedName = parts[parts.length - 1] || 'vault-export.bin'
|
||||
} else {
|
||||
resolvedName = 'vault-export.bin'
|
||||
}
|
||||
}
|
||||
|
||||
// Get the file as an array buffer and convert to Buffer
|
||||
const arrayBuffer = await downloadResponse.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
name: resolvedName,
|
||||
mimeType: contentType,
|
||||
data: buffer,
|
||||
size: buffer.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
file: { type: 'file', description: 'Downloaded Vault export file stored in execution files' },
|
||||
},
|
||||
}
|
||||
7
apps/sim/tools/google_vault/index.ts
Normal file
7
apps/sim/tools/google_vault/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { createMattersTool } from '@/tools/google_vault/create_matters'
|
||||
export { createMattersExportTool } from '@/tools/google_vault/create_matters_export'
|
||||
export { createMattersHoldsTool } from '@/tools/google_vault/create_matters_holds'
|
||||
export { downloadExportFileTool } from '@/tools/google_vault/download_export_file'
|
||||
export { listMattersTool } from '@/tools/google_vault/list_matters'
|
||||
export { listMattersExportTool } from '@/tools/google_vault/list_matters_export'
|
||||
export { listMattersHoldsTool } from '@/tools/google_vault/list_matters_holds'
|
||||
57
apps/sim/tools/google_vault/list_matters.ts
Normal file
57
apps/sim/tools/google_vault/list_matters.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
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<GoogleVaultListMattersParams> = {
|
||||
id: 'list_matters',
|
||||
name: 'Vault List Matters',
|
||||
description: 'List matters, or get a specific matter if matterId is provided',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
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' },
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.matterId) {
|
||||
return `https://vault.googleapis.com/v1/matters/${params.matterId}`
|
||||
}
|
||||
const url = new URL('https://vault.googleapis.com/v1/matters')
|
||||
// Handle pageSize - convert to number if needed
|
||||
if (params.pageSize !== undefined && params.pageSize !== null) {
|
||||
const pageSize = Number(params.pageSize)
|
||||
if (Number.isFinite(pageSize) && pageSize > 0) {
|
||||
url.searchParams.set('pageSize', String(pageSize))
|
||||
}
|
||||
}
|
||||
if (params.pageToken) url.searchParams.set('pageToken', params.pageToken)
|
||||
// Default BASIC view implicitly by omitting 'view' and 'state' params
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to list matters')
|
||||
}
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
55
apps/sim/tools/google_vault/list_matters_export.ts
Normal file
55
apps/sim/tools/google_vault/list_matters_export.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { GoogleVaultListMattersExportParams } from '@/tools/google_vault/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
// matters.exports.list
|
||||
// GET https://vault.googleapis.com/v1/matters/{matterId}/exports
|
||||
export const listMattersExportTool: ToolConfig<GoogleVaultListMattersExportParams> = {
|
||||
id: 'list_matters_export',
|
||||
name: 'Vault List Exports (by Matter)',
|
||||
description: 'List exports for a matter',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
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' },
|
||||
exportId: { type: 'string', required: false, visibility: 'user-only' },
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.exportId) {
|
||||
return `https://vault.googleapis.com/v1/matters/${params.matterId}/exports/${params.exportId}`
|
||||
}
|
||||
const url = new URL(`https://vault.googleapis.com/v1/matters/${params.matterId}/exports`)
|
||||
// Handle pageSize - convert to number if needed
|
||||
if (params.pageSize !== undefined && params.pageSize !== null) {
|
||||
const pageSize = Number(params.pageSize)
|
||||
if (Number.isFinite(pageSize) && pageSize > 0) {
|
||||
url.searchParams.set('pageSize', String(pageSize))
|
||||
}
|
||||
}
|
||||
if (params.pageToken) url.searchParams.set('pageToken', params.pageToken)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to list exports')
|
||||
}
|
||||
|
||||
// Return the raw API response without modifications
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
52
apps/sim/tools/google_vault/list_matters_holds.ts
Normal file
52
apps/sim/tools/google_vault/list_matters_holds.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { GoogleVaultListMattersHoldsParams } from '@/tools/google_vault/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listMattersHoldsTool: ToolConfig<GoogleVaultListMattersHoldsParams> = {
|
||||
id: 'list_matters_holds',
|
||||
name: 'Vault List Holds (by Matter)',
|
||||
description: 'List holds for a matter',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-vault',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/ediscovery'],
|
||||
},
|
||||
|
||||
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' },
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.holdId) {
|
||||
return `https://vault.googleapis.com/v1/matters/${params.matterId}/holds/${params.holdId}`
|
||||
}
|
||||
const url = new URL(`https://vault.googleapis.com/v1/matters/${params.matterId}/holds`)
|
||||
// Handle pageSize - convert to number if needed
|
||||
if (params.pageSize !== undefined && params.pageSize !== null) {
|
||||
const pageSize = Number(params.pageSize)
|
||||
if (Number.isFinite(pageSize) && pageSize > 0) {
|
||||
url.searchParams.set('pageSize', String(pageSize))
|
||||
}
|
||||
}
|
||||
if (params.pageToken) url.searchParams.set('pageToken', params.pageToken)
|
||||
// Default BASIC_HOLD implicitly by omitting 'view'
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({ Authorization: `Bearer ${params.accessToken}` }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to list holds')
|
||||
}
|
||||
return { success: true, output: data }
|
||||
},
|
||||
}
|
||||
52
apps/sim/tools/google_vault/types.ts
Normal file
52
apps/sim/tools/google_vault/types.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface GoogleVaultCommonParams {
|
||||
accessToken: string
|
||||
matterId: string
|
||||
}
|
||||
|
||||
// Exports
|
||||
export interface GoogleVaultCreateMattersExportParams extends GoogleVaultCommonParams {
|
||||
exportName: string
|
||||
corpus: GoogleVaultCorpus
|
||||
accountEmails?: string // Comma-separated list or array handled in the tool
|
||||
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
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
export interface GoogleVaultCreateMattersHoldsParams extends GoogleVaultCommonParams {
|
||||
holdName: string
|
||||
corpus: GoogleVaultCorpus
|
||||
accountEmails?: string // Comma-separated list or array handled in the tool
|
||||
orgUnitId?: string
|
||||
}
|
||||
|
||||
export interface GoogleVaultListMattersHoldsParams extends GoogleVaultCommonParams {
|
||||
pageSize?: number
|
||||
pageToken?: string
|
||||
holdId?: string // Short input to fetch a specific hold
|
||||
}
|
||||
|
||||
export interface GoogleVaultListMattersHoldsResponse extends ToolResponse {
|
||||
output: any
|
||||
}
|
||||
@@ -236,6 +236,14 @@ export async function executeTool(
|
||||
`[${requestId}] Successfully got access token for ${toolId}, length: ${data.accessToken?.length || 0}`
|
||||
)
|
||||
|
||||
// Preserve credential for downstream transforms while removing it from request payload
|
||||
// so we don't leak it to external services.
|
||||
if (contextParams.credential) {
|
||||
;(contextParams as any)._credentialId = contextParams.credential
|
||||
}
|
||||
if (workflowId) {
|
||||
;(contextParams as any)._workflowId = workflowId
|
||||
}
|
||||
// Clean up params we don't need to pass to the actual tool
|
||||
contextParams.credential = undefined
|
||||
if (contextParams.workflowId) contextParams.workflowId = undefined
|
||||
|
||||
@@ -55,6 +55,15 @@ import {
|
||||
googleSheetsUpdateTool,
|
||||
googleSheetsWriteTool,
|
||||
} from '@/tools/google_sheets'
|
||||
import {
|
||||
createMattersExportTool,
|
||||
createMattersHoldsTool,
|
||||
createMattersTool,
|
||||
downloadExportFileTool,
|
||||
listMattersExportTool,
|
||||
listMattersHoldsTool,
|
||||
listMattersTool,
|
||||
} from '@/tools/google_vault'
|
||||
import { requestTool as httpRequest } from '@/tools/http'
|
||||
import { huggingfaceChatTool } from '@/tools/huggingface'
|
||||
import {
|
||||
@@ -356,6 +365,13 @@ export const tools: Record<string, ToolConfig> = {
|
||||
wikipedia_search: wikipediaSearchTool,
|
||||
wikipedia_content: wikipediaPageContentTool,
|
||||
wikipedia_random: wikipediaRandomPageTool,
|
||||
google_vault_create_matters_export: createMattersExportTool,
|
||||
google_vault_list_matters_export: listMattersExportTool,
|
||||
google_vault_create_matters_holds: createMattersHoldsTool,
|
||||
google_vault_list_matters_holds: listMattersHoldsTool,
|
||||
google_vault_create_matters: createMattersTool,
|
||||
google_vault_list_matters: listMattersTool,
|
||||
google_vault_download_export_file: downloadExportFileTool,
|
||||
qdrant_fetch_points: qdrantFetchTool,
|
||||
qdrant_search_vector: qdrantSearchTool,
|
||||
qdrant_upsert_points: qdrantUpsertTool,
|
||||
|
||||
Reference in New Issue
Block a user