From cd88706ea43ff4c90352b0a0b672ddd4dad1acb9 Mon Sep 17 00:00:00 2001 From: Waleed Date: Sun, 1 Mar 2026 23:43:09 -0800 Subject: [PATCH 1/6] fix(icons): fix pagerduty icon (#3392) --- apps/docs/components/icons.tsx | 2 +- apps/sim/components/icons.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index c4666fba17..90d3718291 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2467,7 +2467,7 @@ export function PagerDutyIcon(props: SVGProps) { ) diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index c4666fba17..90d3718291 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2467,7 +2467,7 @@ export function PagerDutyIcon(props: SVGProps) { ) From afaa3618017f3428f47d409c03721a612b3c5af5 Mon Sep 17 00:00:00 2001 From: Waleed Date: Mon, 2 Mar 2026 10:58:21 -0800 Subject: [PATCH 2/6] improvement(airtable): added more tools (#3396) --- apps/docs/content/docs/en/tools/airtable.mdx | 92 +++++++++++-- .../components/oauth-required-modal.tsx | 1 + apps/sim/blocks/blocks/airtable.ts | 25 +++- apps/sim/lib/auth/auth.ts | 8 +- apps/sim/lib/oauth/oauth.ts | 8 +- apps/sim/tools/airtable/create_records.ts | 18 ++- apps/sim/tools/airtable/get_record.ts | 14 +- apps/sim/tools/airtable/index.ts | 6 + apps/sim/tools/airtable/list_bases.ts | 91 +++++++++++++ apps/sim/tools/airtable/list_records.ts | 24 ++-- apps/sim/tools/airtable/list_tables.ts | 124 ++++++++++++++++++ apps/sim/tools/airtable/types.ts | 75 +++++++++++ .../tools/airtable/update_multiple_records.ts | 25 ++-- apps/sim/tools/airtable/update_record.ts | 20 ++- apps/sim/tools/registry.ts | 6 + 15 files changed, 485 insertions(+), 52 deletions(-) create mode 100644 apps/sim/tools/airtable/list_bases.ts create mode 100644 apps/sim/tools/airtable/list_tables.ts diff --git a/apps/docs/content/docs/en/tools/airtable.mdx b/apps/docs/content/docs/en/tools/airtable.mdx index 879b10b583..ff062717a6 100644 --- a/apps/docs/content/docs/en/tools/airtable.mdx +++ b/apps/docs/content/docs/en/tools/airtable.mdx @@ -26,12 +26,63 @@ In Sim, the Airtable integration enables your agents to interact with your Airta ## Usage Instructions -Integrates Airtable into the workflow. Can create, get, list, or update Airtable records. Can be used in trigger mode to trigger a workflow when an update is made to an Airtable table. +Integrates Airtable into the workflow. Can list bases, list tables (with schema), and create, get, list, or update records. Can also be used in trigger mode to trigger a workflow when an update is made to an Airtable table. ## Tools +### `airtable_list_bases` + +List all Airtable bases the user has access to + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `offset` | string | No | Pagination offset for retrieving additional bases | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `bases` | array | List of Airtable bases | +| ↳ `id` | string | Base ID \(starts with "app"\) | +| ↳ `name` | string | Base name | +| ↳ `permissionLevel` | string | Permission level \(none, read, comment, edit, create\) | +| `metadata` | json | Pagination and count metadata | +| ↳ `offset` | string | Offset for next page of results | +| ↳ `totalBases` | number | Number of bases returned | + +### `airtable_list_tables` + +List all tables and their schema in an Airtable base + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `baseId` | string | Yes | Airtable base ID \(starts with "app", e.g., "appXXXXXXXXXXXXXX"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tables` | array | List of tables in the base with their schema | +| ↳ `id` | string | Table ID \(starts with "tbl"\) | +| ↳ `name` | string | Table name | +| ↳ `description` | string | Table description | +| ↳ `primaryFieldId` | string | ID of the primary field | +| ↳ `fields` | array | List of fields in the table | +| ↳ `id` | string | Field ID \(starts with "fld"\) | +| ↳ `name` | string | Field name | +| ↳ `type` | string | Field type \(singleLineText, multilineText, number, checkbox, singleSelect, multipleSelects, date, dateTime, attachment, linkedRecord, etc.\) | +| ↳ `description` | string | Field description | +| ↳ `options` | json | Field-specific options \(choices, etc.\) | +| `metadata` | json | Base info and count metadata | +| ↳ `baseId` | string | The base ID queried | +| ↳ `totalTables` | number | Number of tables in the base | + ### `airtable_list_records` Read records from an Airtable table @@ -49,8 +100,13 @@ Read records from an Airtable table | Parameter | Type | Description | | --------- | ---- | ----------- | -| `records` | json | Array of retrieved Airtable records | +| `records` | array | Array of retrieved Airtable records | +| ↳ `id` | string | Record ID | +| ↳ `createdTime` | string | Record creation timestamp | +| ↳ `fields` | json | Record field values | | `metadata` | json | Operation metadata including pagination offset and total records count | +| ↳ `offset` | string | Pagination offset for next page | +| ↳ `totalRecords` | number | Number of records returned | ### `airtable_get_record` @@ -68,8 +124,12 @@ Retrieve a single record from an Airtable table by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `record` | json | Retrieved Airtable record with id, createdTime, and fields | -| `metadata` | json | Operation metadata including record count | +| `record` | json | Retrieved Airtable record | +| ↳ `id` | string | Record ID | +| ↳ `createdTime` | string | Record creation timestamp | +| ↳ `fields` | json | Record field values | +| `metadata` | json | Operation metadata | +| ↳ `recordCount` | number | Number of records returned \(always 1\) | ### `airtable_create_records` @@ -88,8 +148,12 @@ Write new records to an Airtable table | Parameter | Type | Description | | --------- | ---- | ----------- | -| `records` | json | Array of created Airtable records | +| `records` | array | Array of created Airtable records | +| ↳ `id` | string | Record ID | +| ↳ `createdTime` | string | Record creation timestamp | +| ↳ `fields` | json | Record field values | | `metadata` | json | Operation metadata | +| ↳ `recordCount` | number | Number of records created | ### `airtable_update_record` @@ -108,8 +172,13 @@ Update an existing record in an Airtable table by ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `record` | json | Updated Airtable record with id, createdTime, and fields | -| `metadata` | json | Operation metadata including record count and updated field names | +| `record` | json | Updated Airtable record | +| ↳ `id` | string | Record ID | +| ↳ `createdTime` | string | Record creation timestamp | +| ↳ `fields` | json | Record field values | +| `metadata` | json | Operation metadata | +| ↳ `recordCount` | number | Number of records updated \(always 1\) | +| ↳ `updatedFields` | array | List of field names that were updated | ### `airtable_update_multiple_records` @@ -127,7 +196,12 @@ Update multiple existing records in an Airtable table | Parameter | Type | Description | | --------- | ---- | ----------- | -| `records` | json | Array of updated Airtable records | -| `metadata` | json | Operation metadata including record count and updated record IDs | +| `records` | array | Array of updated Airtable records | +| ↳ `id` | string | Record ID | +| ↳ `createdTime` | string | Record creation timestamp | +| ↳ `fields` | json | Record field values | +| `metadata` | json | Operation metadata | +| ↳ `recordCount` | number | Number of records updated | +| ↳ `updatedRecordIds` | array | List of updated record IDs | diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx index fb340a877b..c2ebf9b68b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx @@ -119,6 +119,7 @@ const SCOPE_DESCRIPTIONS: Record = { 'offline.access': 'Access account when not using the application', 'data.records:read': 'Read records', 'data.records:write': 'Write to records', + 'schema.bases:read': 'View bases and tables', 'webhook:manage': 'Manage webhooks', 'page.read': 'Read Notion pages', 'page.write': 'Write to Notion pages', diff --git a/apps/sim/blocks/blocks/airtable.ts b/apps/sim/blocks/blocks/airtable.ts index 03a4a6f7b7..63fc14b33d 100644 --- a/apps/sim/blocks/blocks/airtable.ts +++ b/apps/sim/blocks/blocks/airtable.ts @@ -10,7 +10,7 @@ export const AirtableBlock: BlockConfig = { description: 'Read, create, and update Airtable', authMode: AuthMode.OAuth, longDescription: - 'Integrates Airtable into the workflow. Can create, get, list, or update Airtable records. Can be used in trigger mode to trigger a workflow when an update is made to an Airtable table.', + 'Integrates Airtable into the workflow. Can list bases, list tables (with schema), and create, get, list, or update records. Can also be used in trigger mode to trigger a workflow when an update is made to an Airtable table.', docsLink: 'https://docs.sim.ai/tools/airtable', category: 'tools', bgColor: '#E0E0E0', @@ -21,10 +21,13 @@ export const AirtableBlock: BlockConfig = { title: 'Operation', type: 'dropdown', options: [ + { label: 'List Bases', id: 'listBases' }, + { label: 'List Tables', id: 'listTables' }, { label: 'List Records', id: 'list' }, { label: 'Get Record', id: 'get' }, { label: 'Create Records', id: 'create' }, { label: 'Update Record', id: 'update' }, + { label: 'Update Multiple Records', id: 'updateMultiple' }, ], value: () => 'list', }, @@ -38,6 +41,7 @@ export const AirtableBlock: BlockConfig = { requiredScopes: [ 'data.records:read', 'data.records:write', + 'schema.bases:read', 'user.email:read', 'webhook:manage', ], @@ -59,7 +63,8 @@ export const AirtableBlock: BlockConfig = { type: 'short-input', placeholder: 'Enter your base ID (e.g., appXXXXXXXXXXXXXX)', dependsOn: ['credential'], - required: true, + condition: { field: 'operation', value: 'listBases', not: true }, + required: { field: 'operation', value: 'listBases', not: true }, }, { id: 'tableId', @@ -67,7 +72,8 @@ export const AirtableBlock: BlockConfig = { type: 'short-input', placeholder: 'Enter table ID (e.g., tblXXXXXXXXXXXXXX)', dependsOn: ['credential', 'baseId'], - required: true, + condition: { field: 'operation', value: ['listBases', 'listTables'], not: true }, + required: { field: 'operation', value: ['listBases', 'listTables'], not: true }, }, { id: 'recordId', @@ -83,6 +89,7 @@ export const AirtableBlock: BlockConfig = { type: 'short-input', placeholder: 'Maximum records to return', condition: { field: 'operation', value: 'list' }, + mode: 'advanced', }, { id: 'filterFormula', @@ -90,6 +97,7 @@ export const AirtableBlock: BlockConfig = { type: 'long-input', placeholder: 'Airtable formula to filter records (optional)', condition: { field: 'operation', value: 'list' }, + mode: 'advanced', wandConfig: { enabled: true, prompt: `Generate an Airtable filter formula based on the user's description. @@ -206,6 +214,8 @@ Return ONLY the valid JSON object - no explanations, no markdown.`, ], tools: { access: [ + 'airtable_list_bases', + 'airtable_list_tables', 'airtable_list_records', 'airtable_get_record', 'airtable_create_records', @@ -215,6 +225,10 @@ Return ONLY the valid JSON object - no explanations, no markdown.`, config: { tool: (params) => { switch (params.operation) { + case 'listBases': + return 'airtable_list_bases' + case 'listTables': + return 'airtable_list_tables' case 'list': return 'airtable_list_records' case 'get': @@ -278,6 +292,11 @@ Return ONLY the valid JSON object - no explanations, no markdown.`, }, // Output structure depends on the operation, covered by AirtableResponse union type outputs: { + // List Bases output + bases: { type: 'json', description: 'List of accessible Airtable bases' }, + // List Tables output + tables: { type: 'json', description: 'List of tables in the base with schema' }, + // Record outputs records: { type: 'json', description: 'Retrieved record data' }, // Optional: for list, create, updateMultiple record: { type: 'json', description: 'Single record data' }, // Optional: for get, update single metadata: { type: 'json', description: 'Operation metadata' }, // Required: present in all responses diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index b0f498b0b4..63ddffabc5 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -2155,7 +2155,13 @@ export const auth = betterAuth({ authorizationUrl: 'https://airtable.com/oauth2/v1/authorize', tokenUrl: 'https://airtable.com/oauth2/v1/token', userInfoUrl: 'https://api.airtable.com/v0/meta/whoami', - scopes: ['data.records:read', 'data.records:write', 'user.email:read', 'webhook:manage'], + scopes: [ + 'data.records:read', + 'data.records:write', + 'schema.bases:read', + 'user.email:read', + 'webhook:manage', + ], responseType: 'code', pkce: true, accessType: 'offline', diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index e1d3f862aa..652269afcb 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -486,7 +486,13 @@ export const OAUTH_PROVIDERS: Record = { providerId: 'airtable', icon: AirtableIcon, baseProviderIcon: AirtableIcon, - scopes: ['data.records:read', 'data.records:write', 'user.email:read', 'webhook:manage'], + scopes: [ + 'data.records:read', + 'data.records:write', + 'schema.bases:read', + 'user.email:read', + 'webhook:manage', + ], }, }, defaultService: 'airtable', diff --git a/apps/sim/tools/airtable/create_records.ts b/apps/sim/tools/airtable/create_records.ts index d67fdcd65f..cbb4b30535 100644 --- a/apps/sim/tools/airtable/create_records.ts +++ b/apps/sim/tools/airtable/create_records.ts @@ -41,7 +41,8 @@ export const airtableCreateRecordsTool: ToolConfig `https://api.airtable.com/v0/${params.baseId}/${params.tableId}`, + url: (params) => + `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -55,9 +56,9 @@ export const airtableCreateRecordsTool: ToolConfig - `https://api.airtable.com/v0/${params.baseId}/${params.tableId}/${params.recordId}`, + `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}/${params.recordId?.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -65,11 +65,19 @@ export const airtableGetRecordTool: ToolConfig = + { + id: 'airtable_list_bases', + name: 'Airtable List Bases', + description: 'List all Airtable bases the user has access to', + version: '1.0.0', + + oauth: { + required: true, + provider: 'airtable', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + offset: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination offset for retrieving additional bases', + }, + }, + + request: { + url: (params) => { + const url = 'https://api.airtable.com/v0/meta/bases' + if (params.offset) { + return `${url}?offset=${encodeURIComponent(params.offset)}` + } + return url + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + return { + success: true, + output: { + bases: (data.bases ?? []).map( + (base: { id: string; name: string; permissionLevel: string }) => ({ + id: base.id, + name: base.name, + permissionLevel: base.permissionLevel, + }) + ), + metadata: { + offset: data.offset ?? null, + totalBases: (data.bases ?? []).length, + }, + }, + } + }, + + outputs: { + bases: { + type: 'array', + description: 'List of Airtable bases', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Base ID (starts with "app")' }, + name: { type: 'string', description: 'Base name' }, + permissionLevel: { + type: 'string', + description: 'Permission level (none, read, comment, edit, create)', + }, + }, + }, + }, + metadata: { + type: 'json', + description: 'Pagination and count metadata', + properties: { + offset: { type: 'string', description: 'Offset for next page of results' }, + totalBases: { type: 'number', description: 'Number of bases returned' }, + }, + }, + }, + } diff --git a/apps/sim/tools/airtable/list_records.ts b/apps/sim/tools/airtable/list_records.ts index ff49f99c2b..85872afbeb 100644 --- a/apps/sim/tools/airtable/list_records.ts +++ b/apps/sim/tools/airtable/list_records.ts @@ -47,14 +47,10 @@ export const airtableListRecordsTool: ToolConfig { - const url = `https://api.airtable.com/v0/${params.baseId}/${params.tableId}` + const url = `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}` const queryParams = new URLSearchParams() if (params.maxRecords) queryParams.append('maxRecords', Number(params.maxRecords).toString()) if (params.filterFormula) { - // Airtable formulas often contain characters needing encoding, - // but standard encodeURIComponent might over-encode. - // Simple replacement for single quotes is often sufficient. - // More complex formulas might need careful encoding. const encodedFormula = params.filterFormula.replace(/'/g, "'") queryParams.append('filterByFormula', encodedFormula) } @@ -74,10 +70,10 @@ export const airtableListRecordsTool: ToolConfig = { + id: 'airtable_list_tables', + name: 'Airtable List Tables', + description: 'List all tables and their schema in an Airtable base', + version: '1.0.0', + + oauth: { + required: true, + provider: 'airtable', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token', + }, + baseId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Airtable base ID (starts with "app", e.g., "appXXXXXXXXXXXXXX")', + }, + }, + + request: { + url: (params) => `https://api.airtable.com/v0/meta/bases/${params.baseId?.trim()}/tables`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response, params) => { + const data = await response.json() + const tables: AirtableTable[] = (data.tables ?? []).map( + (table: { + id: string + name: string + description?: string + primaryFieldId: string + fields: AirtableField[] + }) => ({ + id: table.id, + name: table.name, + description: table.description ?? null, + primaryFieldId: table.primaryFieldId, + fields: (table.fields ?? []).map((field: AirtableField) => ({ + id: field.id, + name: field.name, + type: field.type, + description: field.description ?? null, + options: field.options ?? null, + })), + }) + ) + + return { + success: true, + output: { + tables, + metadata: { + baseId: params?.baseId ?? '', + totalTables: tables.length, + }, + }, + } + }, + + outputs: { + tables: { + type: 'array', + description: 'List of tables in the base with their schema', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Table ID (starts with "tbl")' }, + name: { type: 'string', description: 'Table name' }, + description: { type: 'string', description: 'Table description' }, + primaryFieldId: { type: 'string', description: 'ID of the primary field' }, + fields: { + type: 'array', + description: 'List of fields in the table', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Field ID (starts with "fld")' }, + name: { type: 'string', description: 'Field name' }, + type: { + type: 'string', + description: + 'Field type (singleLineText, multilineText, number, checkbox, singleSelect, multipleSelects, date, dateTime, attachment, linkedRecord, etc.)', + }, + description: { type: 'string', description: 'Field description' }, + options: { type: 'json', description: 'Field-specific options (choices, etc.)' }, + }, + }, + }, + }, + }, + }, + metadata: { + type: 'json', + description: 'Base info and count metadata', + properties: { + baseId: { type: 'string', description: 'The base ID queried' }, + totalTables: { type: 'number', description: 'Number of tables in the base' }, + }, + }, + }, +} diff --git a/apps/sim/tools/airtable/types.ts b/apps/sim/tools/airtable/types.ts index 5c12658555..5b28dbc1fb 100644 --- a/apps/sim/tools/airtable/types.ts +++ b/apps/sim/tools/airtable/types.ts @@ -7,12 +7,85 @@ export interface AirtableRecord { fields: Record } +export interface AirtableBase { + id: string + name: string + permissionLevel: 'none' | 'read' | 'comment' | 'edit' | 'create' +} + +export interface AirtableFieldOption { + id: string + name: string + color?: string +} + +export interface AirtableField { + id: string + name: string + type: string + description?: string + options?: { + choices?: AirtableFieldOption[] + linkedTableId?: string + isReversed?: boolean + prefersSingleRecordLink?: boolean + inverseLinkFieldId?: string + [key: string]: unknown + } +} + +export interface AirtableTable { + id: string + name: string + description?: string + primaryFieldId: string + fields: AirtableField[] +} + +export interface AirtableView { + id: string + name: string + type: string +} + interface AirtableBaseParams { accessToken: string baseId: string tableId: string } +// List Bases Types +export interface AirtableListBasesParams { + accessToken: string + offset?: string +} + +export interface AirtableListBasesResponse extends ToolResponse { + output: { + bases: AirtableBase[] + metadata: { + offset?: string + totalBases: number + } + } +} + +// List Tables Types (Get Base Schema) +export interface AirtableListTablesParams { + accessToken: string + baseId: string +} + +export interface AirtableListTablesResponse extends ToolResponse { + output: { + tables: AirtableTable[] + metadata: { + baseId: string + totalTables: number + } + } +} + // List Records Types export interface AirtableListParams extends AirtableBaseParams { maxRecords?: number @@ -89,6 +162,8 @@ export interface AirtableUpdateMultipleResponse extends ToolResponse { } export type AirtableResponse = + | AirtableListBasesResponse + | AirtableListTablesResponse | AirtableListResponse | AirtableGetResponse | AirtableCreateResponse diff --git a/apps/sim/tools/airtable/update_multiple_records.ts b/apps/sim/tools/airtable/update_multiple_records.ts index 3e1d4f99e8..f7f244a0b9 100644 --- a/apps/sim/tools/airtable/update_multiple_records.ts +++ b/apps/sim/tools/airtable/update_multiple_records.ts @@ -46,8 +46,8 @@ export const airtableUpdateMultipleRecordsTool: ToolConfig< }, request: { - // The API endpoint uses PATCH for multiple record updates as well - url: (params) => `https://api.airtable.com/v0/${params.baseId}/${params.tableId}`, + url: (params) => + `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -58,13 +58,14 @@ export const airtableUpdateMultipleRecordsTool: ToolConfig< transformResponse: async (response) => { const data = await response.json() + const records = data.records ?? [] return { success: true, output: { - records: data.records || [], // API returns an array of updated records + records, metadata: { - recordCount: (data.records || []).length, - updatedRecordIds: (data.records || []).map((r: any) => r.id), + recordCount: records.length, + updatedRecordIds: records.map((r: { id: string }) => r.id), }, }, } @@ -72,20 +73,24 @@ export const airtableUpdateMultipleRecordsTool: ToolConfig< outputs: { records: { - type: 'json', + type: 'array', description: 'Array of updated Airtable records', items: { type: 'object', properties: { - id: { type: 'string' }, - createdTime: { type: 'string' }, - fields: { type: 'object' }, + id: { type: 'string', description: 'Record ID' }, + createdTime: { type: 'string', description: 'Record creation timestamp' }, + fields: { type: 'json', description: 'Record field values' }, }, }, }, metadata: { type: 'json', - description: 'Operation metadata including record count and updated record IDs', + description: 'Operation metadata', + properties: { + recordCount: { type: 'number', description: 'Number of records updated' }, + updatedRecordIds: { type: 'array', description: 'List of updated record IDs' }, + }, }, }, } diff --git a/apps/sim/tools/airtable/update_record.ts b/apps/sim/tools/airtable/update_record.ts index 3c277fb776..b3d3751066 100644 --- a/apps/sim/tools/airtable/update_record.ts +++ b/apps/sim/tools/airtable/update_record.ts @@ -46,9 +46,8 @@ export const airtableUpdateRecordTool: ToolConfig - `https://api.airtable.com/v0/${params.baseId}/${params.tableId}/${params.recordId}`, + `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}/${params.recordId?.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -62,10 +61,10 @@ export const airtableUpdateRecordTool: ToolConfig = { algolia_delete_by_filter: algoliaDeleteByFilterTool, airtable_create_records: airtableCreateRecordsTool, airtable_get_record: airtableGetRecordTool, + airtable_list_bases: airtableListBasesTool, airtable_list_records: airtableListRecordsTool, + airtable_list_tables: airtableListTablesTool, + airtable_update_multiple_records: airtableUpdateMultipleRecordsTool, airtable_update_record: airtableUpdateRecordTool, attio_assert_record: attioAssertRecordTool, attio_create_comment: attioCreateCommentTool, From e91ab6260a8da9f006ceb25ba2c5ff401104abbc Mon Sep 17 00:00:00 2001 From: Waleed Date: Mon, 2 Mar 2026 11:57:31 -0800 Subject: [PATCH 3/6] fix(layout): polyfill crypto.randomUUID for non-secure HTTP contexts (#3397) --- apps/sim/app/layout.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index 11d1b3036c..33c504a5d3 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -38,6 +38,24 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( + {/* Polyfill crypto.randomUUID for non-secure contexts (HTTP on non-localhost) */} +