diff --git a/apps/sim/blocks/blocks/google_bigquery.ts b/apps/sim/blocks/blocks/google_bigquery.ts index 0f2fad19920..e9a25d93b57 100644 --- a/apps/sim/blocks/blocks/google_bigquery.ts +++ b/apps/sim/blocks/blocks/google_bigquery.ts @@ -3,6 +3,18 @@ import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' +const EXISTING_DATASET_OPS = [ + 'list_tables', + 'get_table', + 'insert_rows', + 'delete_dataset', + 'create_table', + 'delete_table', + 'list_table_data', +] +const EXISTING_TABLE_OPS = ['get_table', 'insert_rows', 'delete_table', 'list_table_data'] +const LOCATION_OPS = ['query', 'get_query_results'] + export const GoogleBigQueryBlock: BlockConfig = { type: 'google_bigquery', name: 'Google BigQuery', @@ -22,9 +34,15 @@ export const GoogleBigQueryBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'Run Query', id: 'query' }, + { label: 'Get Query Results', id: 'get_query_results' }, { label: 'List Datasets', id: 'list_datasets' }, + { label: 'Create Dataset', id: 'create_dataset' }, + { label: 'Delete Dataset', id: 'delete_dataset' }, { label: 'List Tables', id: 'list_tables' }, { label: 'Get Table', id: 'get_table' }, + { label: 'Create Table', id: 'create_table' }, + { label: 'Delete Table', id: 'delete_table' }, + { label: 'List Table Data', id: 'list_table_data' }, { label: 'Insert Rows', id: 'insert_rows' }, ], value: () => 'query', @@ -61,7 +79,7 @@ export const GoogleBigQueryBlock: BlockConfig = { { id: 'query', title: 'SQL Query', - type: 'long-input', + type: 'code', placeholder: 'SELECT * FROM `project.dataset.table` LIMIT 100', condition: { field: 'operation', value: 'query' }, required: { field: 'operation', value: 'query' }, @@ -93,7 +111,10 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, title: 'Max Results', type: 'short-input', placeholder: 'Maximum rows to return', - condition: { field: 'operation', value: ['query', 'list_datasets', 'list_tables'] }, + condition: { + field: 'operation', + value: ['query', 'list_datasets', 'list_tables', 'list_table_data', 'get_query_results'], + }, }, { id: 'defaultDatasetId', @@ -107,7 +128,46 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, title: 'Location', type: 'short-input', placeholder: 'Processing location (e.g., US, EU)', - condition: { field: 'operation', value: 'query' }, + condition: { field: 'operation', value: LOCATION_OPS }, + }, + + { + id: 'jobId', + title: 'Job ID', + type: 'short-input', + placeholder: 'Enter BigQuery job ID', + condition: { field: 'operation', value: 'get_query_results' }, + required: { field: 'operation', value: 'get_query_results' }, + }, + { + id: 'timeoutMs', + title: 'Timeout (ms)', + type: 'short-input', + placeholder: 'How long to wait for the job to complete', + mode: 'advanced', + condition: { field: 'operation', value: 'get_query_results' }, + }, + + { + id: 'newDatasetId', + title: 'Dataset ID', + type: 'short-input', + placeholder: 'ID for the new BigQuery dataset', + condition: { field: 'operation', value: 'create_dataset' }, + required: { field: 'operation', value: 'create_dataset' }, + }, + { + id: 'datasetLocation', + title: 'Location', + type: 'short-input', + placeholder: 'Geographic location (e.g., US, EU)', + condition: { field: 'operation', value: 'create_dataset' }, + }, + { + id: 'deleteContents', + title: 'Delete Contents', + type: 'switch', + condition: { field: 'operation', value: 'delete_dataset' }, }, { @@ -120,8 +180,8 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, placeholder: 'Select BigQuery dataset', dependsOn: ['credential', 'projectId'], mode: 'basic', - condition: { field: 'operation', value: ['list_tables', 'get_table', 'insert_rows'] }, - required: { field: 'operation', value: ['list_tables', 'get_table', 'insert_rows'] }, + condition: { field: 'operation', value: EXISTING_DATASET_OPS }, + required: { field: 'operation', value: EXISTING_DATASET_OPS }, }, { id: 'datasetId', @@ -130,8 +190,8 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, canonicalParamId: 'datasetId', placeholder: 'Enter BigQuery dataset ID', mode: 'advanced', - condition: { field: 'operation', value: ['list_tables', 'get_table', 'insert_rows'] }, - required: { field: 'operation', value: ['list_tables', 'get_table', 'insert_rows'] }, + condition: { field: 'operation', value: EXISTING_DATASET_OPS }, + required: { field: 'operation', value: EXISTING_DATASET_OPS }, }, { @@ -144,8 +204,8 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, placeholder: 'Select BigQuery table', dependsOn: ['credential', 'projectId', 'datasetSelector'], mode: 'basic', - condition: { field: 'operation', value: ['get_table', 'insert_rows'] }, - required: { field: 'operation', value: ['get_table', 'insert_rows'] }, + condition: { field: 'operation', value: EXISTING_TABLE_OPS }, + required: { field: 'operation', value: EXISTING_TABLE_OPS }, }, { id: 'tableId', @@ -154,14 +214,77 @@ Return ONLY the SQL query - no explanations, no quotes, no extra text.`, canonicalParamId: 'tableId', placeholder: 'Enter BigQuery table ID', mode: 'advanced', - condition: { field: 'operation', value: ['get_table', 'insert_rows'] }, - required: { field: 'operation', value: ['get_table', 'insert_rows'] }, + condition: { field: 'operation', value: EXISTING_TABLE_OPS }, + required: { field: 'operation', value: EXISTING_TABLE_OPS }, + }, + + { + id: 'newTableId', + title: 'Table ID', + type: 'short-input', + placeholder: 'ID for the new BigQuery table', + condition: { field: 'operation', value: 'create_table' }, + required: { field: 'operation', value: 'create_table' }, + }, + { + id: 'schema', + title: 'Schema', + type: 'code', + placeholder: '[{"name": "id", "type": "STRING", "mode": "REQUIRED"}]', + condition: { field: 'operation', value: 'create_table' }, + required: { field: 'operation', value: 'create_table' }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of BigQuery column field definitions based on the user's description. +Each field should have "name", "type" (STRING, INTEGER, FLOAT, BOOLEAN, TIMESTAMP, DATE, RECORD, etc.), and optionally "mode" (NULLABLE, REQUIRED, REPEATED) and "description". + +Examples: +- "id and name" -> [{"name": "id", "type": "STRING", "mode": "REQUIRED"}, {"name": "name", "type": "STRING"}] +- "order with amount and timestamp" -> [{"name": "order_id", "type": "STRING", "mode": "REQUIRED"}, {"name": "amount", "type": "FLOAT"}, {"name": "created_at", "type": "TIMESTAMP"}] + +Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, + placeholder: 'Describe the table columns...', + generationType: 'json-object', + }, + }, + { + id: 'friendlyName', + title: 'Friendly Name', + type: 'short-input', + placeholder: 'Human-readable name', + mode: 'advanced', + condition: { field: 'operation', value: ['create_dataset', 'create_table'] }, + }, + { + id: 'description', + title: 'Description', + type: 'short-input', + placeholder: 'Description', + mode: 'advanced', + condition: { field: 'operation', value: ['create_dataset', 'create_table'] }, + }, + + { + id: 'selectedFields', + title: 'Selected Fields', + type: 'short-input', + placeholder: 'Comma-separated list of column names to return', + mode: 'advanced', + condition: { field: 'operation', value: 'list_table_data' }, + }, + { + id: 'startIndex', + title: 'Start Index', + type: 'short-input', + placeholder: 'Zero-based index of the starting row', + mode: 'advanced', + condition: { field: 'operation', value: ['list_table_data', 'get_query_results'] }, }, { id: 'rows', title: 'Rows', - type: 'long-input', + type: 'code', placeholder: '[{"column1": "value1", "column2": 42}]', condition: { field: 'operation', value: 'insert_rows' }, required: { field: 'operation', value: 'insert_rows' }, @@ -197,15 +320,24 @@ Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, title: 'Page Token', type: 'short-input', placeholder: 'Pagination token', - condition: { field: 'operation', value: ['list_datasets', 'list_tables'] }, + condition: { + field: 'operation', + value: ['list_datasets', 'list_tables', 'list_table_data', 'get_query_results'], + }, }, ], tools: { access: [ 'google_bigquery_query', + 'google_bigquery_get_query_results', 'google_bigquery_list_datasets', + 'google_bigquery_create_dataset', + 'google_bigquery_delete_dataset', 'google_bigquery_list_tables', 'google_bigquery_get_table', + 'google_bigquery_create_table', + 'google_bigquery_delete_table', + 'google_bigquery_list_table_data', 'google_bigquery_insert_rows', ], config: { @@ -213,12 +345,24 @@ Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, switch (params.operation) { case 'query': return 'google_bigquery_query' + case 'get_query_results': + return 'google_bigquery_get_query_results' case 'list_datasets': return 'google_bigquery_list_datasets' + case 'create_dataset': + return 'google_bigquery_create_dataset' + case 'delete_dataset': + return 'google_bigquery_delete_dataset' case 'list_tables': return 'google_bigquery_list_tables' case 'get_table': return 'google_bigquery_get_table' + case 'create_table': + return 'google_bigquery_create_table' + case 'delete_table': + return 'google_bigquery_delete_table' + case 'list_table_data': + return 'google_bigquery_list_table_data' case 'insert_rows': return 'google_bigquery_insert_rows' default: @@ -226,12 +370,34 @@ Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, } }, params: (params) => { - const { oauthCredential, rows, maxResults, ...rest } = params + const { + oauthCredential, + rows, + schema, + maxResults, + timeoutMs, + newDatasetId, + newTableId, + datasetLocation, + location, + datasetId, + tableId, + ...rest + } = params + const operation = String(params.operation) return { ...rest, oauthCredential, + ...(LOCATION_OPS.includes(operation) && location && { location }), + ...(EXISTING_DATASET_OPS.includes(operation) && datasetId && { datasetId }), + ...(EXISTING_TABLE_OPS.includes(operation) && tableId && { tableId }), + ...(operation === 'create_dataset' && newDatasetId && { datasetId: newDatasetId }), + ...(operation === 'create_dataset' && datasetLocation && { location: datasetLocation }), + ...(operation === 'create_table' && newTableId && { tableId: newTableId }), ...(rows && { rows: typeof rows === 'string' ? rows : JSON.stringify(rows) }), + ...(schema && { schema: typeof schema === 'string' ? schema : JSON.stringify(schema) }), ...(maxResults !== undefined && maxResults !== '' && { maxResults: Number(maxResults) }), + ...(timeoutMs !== undefined && timeoutMs !== '' && { timeoutMs: Number(timeoutMs) }), } }, }, @@ -248,8 +414,19 @@ Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, description: 'Default dataset for unqualified table names', }, location: { type: 'string', description: 'Processing location' }, + jobId: { type: 'string', description: 'BigQuery job ID' }, + timeoutMs: { type: 'number', description: 'How long to wait for the job to complete' }, + newDatasetId: { type: 'string', description: 'ID for the new BigQuery dataset' }, + datasetLocation: { type: 'string', description: 'Geographic location for the new dataset' }, + deleteContents: { type: 'boolean', description: 'Whether to delete tables inside the dataset' }, datasetId: { type: 'string', description: 'BigQuery dataset ID' }, tableId: { type: 'string', description: 'BigQuery table ID' }, + newTableId: { type: 'string', description: 'ID for the new BigQuery table' }, + schema: { type: 'string', description: 'JSON array of column field definitions' }, + friendlyName: { type: 'string', description: 'Human-readable name' }, + description: { type: 'string', description: 'Description' }, + selectedFields: { type: 'string', description: 'Comma-separated list of column names' }, + startIndex: { type: 'string', description: 'Zero-based index of the starting row' }, rows: { type: 'string', description: 'JSON array of row objects to insert' }, skipInvalidRows: { type: 'boolean', description: 'Whether to skip invalid rows during insert' }, ignoreUnknownValues: { @@ -266,22 +443,46 @@ Return ONLY the JSON array - no explanations, no wrapping, no extra text.`, totalBytesProcessed: { type: 'string', description: 'Bytes processed (query)' }, cacheHit: { type: 'boolean', description: 'Whether result was cached (query)' }, jobReference: { type: 'json', description: 'Job reference for incomplete queries (query)' }, - pageToken: { type: 'string', description: 'Token for additional result pages (query)' }, + pageToken: { + type: 'string', + description: 'Token for additional result pages (query, list_table_data, get_query_results)', + }, datasets: { type: 'json', description: 'Array of dataset objects (list_datasets)' }, tables: { type: 'json', description: 'Array of table objects (list_tables)' }, totalItems: { type: 'number', description: 'Total items count (list_tables)' }, - tableId: { type: 'string', description: 'Table ID (get_table)' }, - datasetId: { type: 'string', description: 'Dataset ID (get_table)' }, - type: { type: 'string', description: 'Table type (get_table)' }, - description: { type: 'string', description: 'Table description (get_table)' }, + tableId: { type: 'string', description: 'Table ID (get_table, create_table)' }, + datasetId: { + type: 'string', + description: 'Dataset ID (get_table, create_table, create_dataset)', + }, + projectId: { + type: 'string', + description: 'Project ID (get_table, create_table, create_dataset)', + }, + type: { type: 'string', description: 'Table type (get_table, create_table)' }, + description: { + type: 'string', + description: 'Table or dataset description (get_table, create_table, create_dataset)', + }, + friendlyName: { type: 'string', description: 'Human-readable name (create_dataset)' }, numRows: { type: 'string', description: 'Row count (get_table)' }, numBytes: { type: 'string', description: 'Size in bytes (get_table)' }, - schema: { type: 'json', description: 'Column definitions (get_table)' }, - creationTime: { type: 'string', description: 'Creation time (get_table)' }, + schema: { type: 'json', description: 'Column definitions (get_table, create_table)' }, + creationTime: { + type: 'string', + description: 'Creation time (get_table, create_table, create_dataset)', + }, lastModifiedTime: { type: 'string', description: 'Last modified time (get_table)' }, - location: { type: 'string', description: 'Data location (get_table)' }, + location: { + type: 'string', + description: 'Data location (get_table, create_table, create_dataset)', + }, insertedRows: { type: 'number', description: 'Rows inserted (insert_rows)' }, errors: { type: 'json', description: 'Insert errors (insert_rows)' }, + deleted: { + type: 'boolean', + description: 'Whether the resource was deleted (delete_dataset, delete_table)', + }, nextPageToken: { type: 'string', description: 'Token for next page of results' }, }, } @@ -381,5 +582,12 @@ export const GoogleBigQueryBlockMeta = { content: '# Load Rows to Table\n\nUse BigQuery to write structured records into a table.\n\n## Steps\n1. Confirm the target dataset and table, and Get Table to verify the expected columns and types.\n2. Shape the incoming records to match the table schema exactly.\n3. Use Insert Rows to write the batch.\n\n## Output\nReturn the count of rows inserted and any rows rejected with their error. If types did not match the schema, report which fields failed rather than silently dropping data.', }, + { + name: 'provision-dataset-and-table', + description: + 'Create a new BigQuery dataset and table with a defined schema for a new pipeline or logging destination.', + content: + '# Provision Dataset and Table\n\nUse BigQuery to set up a new destination for structured data.\n\n## Steps\n1. Use Create Dataset with a dataset ID and location.\n2. Define the column schema (name, type, mode) for the target table.\n3. Use Create Table with that schema inside the new dataset.\n\n## Output\nReturn the created dataset ID, table ID, and the resolved schema so downstream steps can insert rows with confidence the columns match.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/google_bigquery/create_dataset.ts b/apps/sim/tools/google_bigquery/create_dataset.ts new file mode 100644 index 00000000000..c892e5dd501 --- /dev/null +++ b/apps/sim/tools/google_bigquery/create_dataset.ts @@ -0,0 +1,122 @@ +import type { + GoogleBigQueryCreateDatasetParams, + GoogleBigQueryCreateDatasetResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryCreateDatasetTool: ToolConfig< + GoogleBigQueryCreateDatasetParams, + GoogleBigQueryCreateDatasetResponse +> = { + id: 'google_bigquery_create_dataset', + name: 'BigQuery Create Dataset', + description: 'Create a new dataset in a Google BigQuery project', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + datasetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID for the new BigQuery dataset', + }, + location: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Geographic location for the dataset (e.g., "US", "EU")', + }, + friendlyName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Human-readable name for the dataset', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the dataset', + }, + }, + + request: { + url: (params) => + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/datasets`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + datasetReference: { + projectId: params.projectId, + datasetId: params.datasetId.trim(), + }, + } + if (params.location) body.location = params.location + if (params.friendlyName) body.friendlyName = params.friendlyName + if (params.description) body.description = params.description + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + const errorMessage = data.error?.message || 'Failed to create BigQuery dataset' + throw new Error(errorMessage) + } + + return { + success: true, + output: { + datasetId: data.datasetReference?.datasetId ?? null, + projectId: data.datasetReference?.projectId ?? null, + friendlyName: data.friendlyName ?? null, + description: data.description ?? null, + location: data.location ?? null, + creationTime: data.creationTime ?? null, + }, + } + }, + + outputs: { + datasetId: { type: 'string', description: 'Unique dataset identifier' }, + projectId: { type: 'string', description: 'Project ID containing this dataset' }, + friendlyName: { + type: 'string', + description: 'Descriptive name for the dataset', + optional: true, + }, + description: { type: 'string', description: 'Dataset description', optional: true }, + location: { + type: 'string', + description: 'Geographic location where the data resides', + optional: true, + }, + creationTime: { + type: 'string', + description: 'Dataset creation time (milliseconds since epoch)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/google_bigquery/create_table.ts b/apps/sim/tools/google_bigquery/create_table.ts new file mode 100644 index 00000000000..f17fb2f1ea4 --- /dev/null +++ b/apps/sim/tools/google_bigquery/create_table.ts @@ -0,0 +1,173 @@ +import type { + GoogleBigQueryCreateTableParams, + GoogleBigQueryCreateTableResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryCreateTableTool: ToolConfig< + GoogleBigQueryCreateTableParams, + GoogleBigQueryCreateTableResponse +> = { + id: 'google_bigquery_create_table', + name: 'BigQuery Create Table', + description: 'Create a new table in a Google BigQuery dataset', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + datasetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery dataset ID', + }, + tableId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID for the new BigQuery table', + }, + schema: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of column field definitions, e.g. [{"name":"id","type":"STRING","mode":"REQUIRED"}]', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the table', + }, + friendlyName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Human-readable name for the table', + }, + }, + + request: { + url: (params) => + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/datasets/${encodeURIComponent(params.datasetId.trim())}/tables`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + let fields: unknown + try { + fields = typeof params.schema === 'string' ? JSON.parse(params.schema) : params.schema + } catch { + fields = null + } + if ( + !Array.isArray(fields) || + fields.length === 0 || + !fields.every( + (f) => f && typeof f === 'object' && typeof (f as { name?: unknown }).name === 'string' + ) + ) { + throw new Error( + 'Schema must be a JSON array of column field definitions, e.g. [{"name":"id","type":"STRING"}]' + ) + } + + const body: Record = { + tableReference: { + projectId: params.projectId, + datasetId: params.datasetId.trim(), + tableId: params.tableId.trim(), + }, + schema: { fields }, + } + if (params.description) body.description = params.description + if (params.friendlyName) body.friendlyName = params.friendlyName + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + const errorMessage = data.error?.message || 'Failed to create BigQuery table' + throw new Error(errorMessage) + } + + const schema = (data.schema?.fields ?? []).map( + (f: { name: string; type: string; mode?: string; description?: string }) => ({ + name: f.name, + type: f.type, + mode: f.mode ?? null, + description: f.description ?? null, + }) + ) + + return { + success: true, + output: { + tableId: data.tableReference?.tableId ?? null, + datasetId: data.tableReference?.datasetId ?? null, + projectId: data.tableReference?.projectId ?? null, + type: data.type ?? null, + description: data.description ?? null, + schema, + creationTime: data.creationTime ?? null, + location: data.location ?? null, + }, + } + }, + + outputs: { + tableId: { type: 'string', description: 'Table ID' }, + datasetId: { type: 'string', description: 'Dataset ID' }, + projectId: { type: 'string', description: 'Project ID' }, + type: { type: 'string', description: 'Table type (usually TABLE)', optional: true }, + description: { type: 'string', description: 'Table description', optional: true }, + schema: { + type: 'array', + description: 'Array of column definitions', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Column name' }, + type: { type: 'string', description: 'Data type' }, + mode: { + type: 'string', + description: 'Column mode (NULLABLE, REQUIRED, or REPEATED)', + optional: true, + }, + description: { type: 'string', description: 'Column description', optional: true }, + }, + }, + }, + creationTime: { + type: 'string', + description: 'Table creation time (milliseconds since epoch)', + optional: true, + }, + location: { + type: 'string', + description: 'Geographic location where the table resides', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/google_bigquery/delete_dataset.ts b/apps/sim/tools/google_bigquery/delete_dataset.ts new file mode 100644 index 00000000000..fca5affc7b6 --- /dev/null +++ b/apps/sim/tools/google_bigquery/delete_dataset.ts @@ -0,0 +1,82 @@ +import type { + GoogleBigQueryDeleteDatasetParams, + GoogleBigQueryDeleteDatasetResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryDeleteDatasetTool: ToolConfig< + GoogleBigQueryDeleteDatasetParams, + GoogleBigQueryDeleteDatasetResponse +> = { + id: 'google_bigquery_delete_dataset', + name: 'BigQuery Delete Dataset', + description: 'Delete a dataset from a Google BigQuery project', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + datasetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery dataset ID to delete', + }, + deleteContents: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to delete tables inside the dataset (default: false)', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/datasets/${encodeURIComponent(params.datasetId.trim())}` + ) + if (params.deleteContents !== undefined) { + url.searchParams.set('deleteContents', String(params.deleteContents)) + } + return url.toString() + }, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json() + const errorMessage = data.error?.message || 'Failed to delete BigQuery dataset' + throw new Error(errorMessage) + } + + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the dataset was deleted' }, + }, +} diff --git a/apps/sim/tools/google_bigquery/delete_table.ts b/apps/sim/tools/google_bigquery/delete_table.ts new file mode 100644 index 00000000000..5162fb172a1 --- /dev/null +++ b/apps/sim/tools/google_bigquery/delete_table.ts @@ -0,0 +1,75 @@ +import type { + GoogleBigQueryDeleteTableParams, + GoogleBigQueryDeleteTableResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryDeleteTableTool: ToolConfig< + GoogleBigQueryDeleteTableParams, + GoogleBigQueryDeleteTableResponse +> = { + id: 'google_bigquery_delete_table', + name: 'BigQuery Delete Table', + description: 'Delete a table from a Google BigQuery dataset', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + datasetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery dataset ID', + }, + tableId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery table ID to delete', + }, + }, + + request: { + url: (params) => + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/datasets/${encodeURIComponent(params.datasetId.trim())}/tables/${encodeURIComponent(params.tableId.trim())}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const data = await response.json() + const errorMessage = data.error?.message || 'Failed to delete BigQuery table' + throw new Error(errorMessage) + } + + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the table was deleted' }, + }, +} diff --git a/apps/sim/tools/google_bigquery/get_query_results.ts b/apps/sim/tools/google_bigquery/get_query_results.ts new file mode 100644 index 00000000000..f4f0b056e4e --- /dev/null +++ b/apps/sim/tools/google_bigquery/get_query_results.ts @@ -0,0 +1,178 @@ +import type { + GoogleBigQueryGetQueryResultsParams, + GoogleBigQueryGetQueryResultsResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryGetQueryResultsTool: ToolConfig< + GoogleBigQueryGetQueryResultsParams, + GoogleBigQueryGetQueryResultsResponse +> = { + id: 'google_bigquery_get_query_results', + name: 'BigQuery Get Query Results', + description: + 'Fetch results for a previously submitted BigQuery job, or the next page of a Run Query result', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + jobId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the BigQuery job to fetch results for', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Token for pagination', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of rows to return', + }, + timeoutMs: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'How long to wait for the job to complete, in milliseconds', + }, + location: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Processing location of the job (e.g., "US", "EU")', + }, + startIndex: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Zero-based index of the starting row', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/queries/${encodeURIComponent(params.jobId.trim())}` + ) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.maxResults !== undefined && params.maxResults !== null) { + const maxResults = Number(params.maxResults) + if (Number.isFinite(maxResults) && maxResults > 0) { + url.searchParams.set('maxResults', String(maxResults)) + } + } + if (params.timeoutMs !== undefined && params.timeoutMs !== null) { + const timeoutMs = Number(params.timeoutMs) + if (Number.isFinite(timeoutMs) && timeoutMs > 0) { + url.searchParams.set('timeoutMs', String(timeoutMs)) + } + } + if (params.location) url.searchParams.set('location', params.location) + if (params.startIndex) url.searchParams.set('startIndex', params.startIndex) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + const errorMessage = data.error?.message || 'Failed to fetch BigQuery query results' + throw new Error(errorMessage) + } + + const columns = (data.schema?.fields ?? []).map((f: { name: string }) => f.name) + const rows = (data.rows ?? []).map((row: { f: Array<{ v: unknown }> }) => { + const obj: Record = {} + row.f.forEach((field, index) => { + obj[columns[index]] = field.v ?? null + }) + return obj + }) + + return { + success: true, + output: { + columns, + rows, + totalRows: data.totalRows ?? null, + jobComplete: data.jobComplete ?? false, + totalBytesProcessed: data.totalBytesProcessed ?? null, + cacheHit: data.cacheHit ?? null, + jobReference: data.jobReference ?? null, + pageToken: data.pageToken ?? null, + }, + } + }, + + outputs: { + columns: { + type: 'array', + description: 'Array of column names from the query result', + items: { type: 'string', description: 'Column name' }, + }, + rows: { + type: 'array', + description: 'Array of row objects keyed by column name', + items: { + type: 'object', + description: 'Row with column name/value pairs', + }, + }, + totalRows: { + type: 'string', + description: 'Total number of rows in the complete result set', + optional: true, + }, + jobComplete: { type: 'boolean', description: 'Whether the job has completed' }, + totalBytesProcessed: { + type: 'string', + description: 'Total bytes processed by the query', + optional: true, + }, + cacheHit: { + type: 'boolean', + description: 'Whether the query result was served from cache', + optional: true, + }, + jobReference: { + type: 'object', + description: 'Job reference (useful when jobComplete is false)', + optional: true, + properties: { + projectId: { type: 'string', description: 'Project ID containing the job' }, + jobId: { type: 'string', description: 'Unique job identifier' }, + location: { type: 'string', description: 'Geographic location of the job' }, + }, + }, + pageToken: { + type: 'string', + description: 'Token for fetching additional result pages', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/google_bigquery/index.ts b/apps/sim/tools/google_bigquery/index.ts index ea1aa73371a..b88b3afd385 100644 --- a/apps/sim/tools/google_bigquery/index.ts +++ b/apps/sim/tools/google_bigquery/index.ts @@ -1,5 +1,11 @@ +export { googleBigQueryCreateDatasetTool } from '@/tools/google_bigquery/create_dataset' +export { googleBigQueryCreateTableTool } from '@/tools/google_bigquery/create_table' +export { googleBigQueryDeleteDatasetTool } from '@/tools/google_bigquery/delete_dataset' +export { googleBigQueryDeleteTableTool } from '@/tools/google_bigquery/delete_table' +export { googleBigQueryGetQueryResultsTool } from '@/tools/google_bigquery/get_query_results' export { googleBigQueryGetTableTool } from '@/tools/google_bigquery/get_table' export { googleBigQueryInsertRowsTool } from '@/tools/google_bigquery/insert_rows' export { googleBigQueryListDatasetsTool } from '@/tools/google_bigquery/list_datasets' +export { googleBigQueryListTableDataTool } from '@/tools/google_bigquery/list_table_data' export { googleBigQueryListTablesTool } from '@/tools/google_bigquery/list_tables' export { googleBigQueryQueryTool } from '@/tools/google_bigquery/query' diff --git a/apps/sim/tools/google_bigquery/list_table_data.ts b/apps/sim/tools/google_bigquery/list_table_data.ts new file mode 100644 index 00000000000..c93be577048 --- /dev/null +++ b/apps/sim/tools/google_bigquery/list_table_data.ts @@ -0,0 +1,133 @@ +import type { + GoogleBigQueryListTableDataParams, + GoogleBigQueryListTableDataResponse, +} from '@/tools/google_bigquery/types' +import type { ToolConfig } from '@/tools/types' + +export const googleBigQueryListTableDataTool: ToolConfig< + GoogleBigQueryListTableDataParams, + GoogleBigQueryListTableDataResponse +> = { + id: 'google_bigquery_list_table_data', + name: 'BigQuery List Table Data', + description: + 'Preview rows from a Google BigQuery table without running a query. Pair with Get Table to know the column order.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-bigquery', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Cloud project ID', + }, + datasetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery dataset ID', + }, + tableId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'BigQuery table ID', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of rows to return', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Token for pagination', + }, + startIndex: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Zero-based index of the starting row', + }, + selectedFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of column names to return', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(params.projectId)}/datasets/${encodeURIComponent(params.datasetId.trim())}/tables/${encodeURIComponent(params.tableId.trim())}/data` + ) + if (params.maxResults !== undefined && params.maxResults !== null) { + const maxResults = Number(params.maxResults) + if (Number.isFinite(maxResults) && maxResults > 0) { + url.searchParams.set('maxResults', String(maxResults)) + } + } + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.startIndex) url.searchParams.set('startIndex', params.startIndex) + if (params.selectedFields) url.searchParams.set('selectedFields', params.selectedFields) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + const errorMessage = data.error?.message || 'Failed to list BigQuery table data' + throw new Error(errorMessage) + } + + const rows = (data.rows ?? []).map((row: { f: Array<{ v: unknown }> }) => + row.f.map((field) => field.v ?? null) + ) + + return { + success: true, + output: { + rows, + totalRows: data.totalRows ?? null, + pageToken: data.pageToken ?? null, + }, + } + }, + + outputs: { + rows: { + type: 'array', + description: 'Array of rows, each a raw array of column values in schema order', + items: { type: 'array', description: 'Row values in column order' }, + }, + totalRows: { + type: 'string', + description: 'Total number of rows in the table', + optional: true, + }, + pageToken: { + type: 'string', + description: 'Token for fetching the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/google_bigquery/types.ts b/apps/sim/tools/google_bigquery/types.ts index 2f2a6515ecf..3ffc605bac6 100644 --- a/apps/sim/tools/google_bigquery/types.ts +++ b/apps/sim/tools/google_bigquery/types.ts @@ -37,6 +37,49 @@ export interface GoogleBigQueryInsertRowsParams extends GoogleBigQueryBaseParams ignoreUnknownValues?: boolean } +export interface GoogleBigQueryCreateDatasetParams extends GoogleBigQueryBaseParams { + datasetId: string + location?: string + friendlyName?: string + description?: string +} + +export interface GoogleBigQueryDeleteDatasetParams extends GoogleBigQueryBaseParams { + datasetId: string + deleteContents?: boolean +} + +export interface GoogleBigQueryCreateTableParams extends GoogleBigQueryBaseParams { + datasetId: string + tableId: string + schema: string + description?: string + friendlyName?: string +} + +export interface GoogleBigQueryDeleteTableParams extends GoogleBigQueryBaseParams { + datasetId: string + tableId: string +} + +export interface GoogleBigQueryListTableDataParams extends GoogleBigQueryBaseParams { + datasetId: string + tableId: string + maxResults?: number + pageToken?: string + startIndex?: string + selectedFields?: string +} + +export interface GoogleBigQueryGetQueryResultsParams extends GoogleBigQueryBaseParams { + jobId: string + pageToken?: string + maxResults?: number + timeoutMs?: number + location?: string + startIndex?: string +} + interface GoogleBigQueryJobReference { projectId: string jobId: string @@ -117,3 +160,65 @@ export interface GoogleBigQueryInsertRowsResponse extends ToolResponse { }> } } + +export interface GoogleBigQueryCreateDatasetResponse extends ToolResponse { + output: { + datasetId: string + projectId: string + friendlyName: string | null + description: string | null + location: string | null + creationTime: string | null + } +} + +export interface GoogleBigQueryDeleteDatasetResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface GoogleBigQueryCreateTableResponse extends ToolResponse { + output: { + tableId: string + datasetId: string + projectId: string + type: string | null + description: string | null + schema: Array<{ + name: string + type: string + mode: string | null + description: string | null + }> + creationTime: string | null + location: string | null + } +} + +export interface GoogleBigQueryDeleteTableResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface GoogleBigQueryListTableDataResponse extends ToolResponse { + output: { + rows: unknown[][] + totalRows: string | null + pageToken: string | null + } +} + +export interface GoogleBigQueryGetQueryResultsResponse extends ToolResponse { + output: { + columns: string[] + rows: Record[] + totalRows: string | null + jobComplete: boolean + totalBytesProcessed: string | null + cacheHit: boolean | null + jobReference: GoogleBigQueryJobReference | null + pageToken: string | null + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 02c6f7c8e7b..0127ea51bd9 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1304,9 +1304,15 @@ import { googleAppsheetFindRowsTool, } from '@/tools/google_appsheet' import { + googleBigQueryCreateDatasetTool, + googleBigQueryCreateTableTool, + googleBigQueryDeleteDatasetTool, + googleBigQueryDeleteTableTool, + googleBigQueryGetQueryResultsTool, googleBigQueryGetTableTool, googleBigQueryInsertRowsTool, googleBigQueryListDatasetsTool, + googleBigQueryListTableDataTool, googleBigQueryListTablesTool, googleBigQueryQueryTool, } from '@/tools/google_bigquery' @@ -7738,6 +7744,12 @@ export const tools: Record = { google_bigquery_list_tables: googleBigQueryListTablesTool, google_bigquery_get_table: googleBigQueryGetTableTool, google_bigquery_insert_rows: googleBigQueryInsertRowsTool, + google_bigquery_create_dataset: googleBigQueryCreateDatasetTool, + google_bigquery_delete_dataset: googleBigQueryDeleteDatasetTool, + google_bigquery_create_table: googleBigQueryCreateTableTool, + google_bigquery_delete_table: googleBigQueryDeleteTableTool, + google_bigquery_list_table_data: googleBigQueryListTableDataTool, + google_bigquery_get_query_results: googleBigQueryGetQueryResultsTool, google_vault_create_matters_export: createMattersExportTool, google_vault_list_matters_export: listMattersExportTool, google_vault_create_matters_holds: createMattersHoldsTool,