chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
import type { AirtableCreateParams, AirtableCreateResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableCreateRecordsTool: ToolConfig<AirtableCreateParams, AirtableCreateResponse> = {
id: 'airtable_create_records',
name: 'Airtable Create Records',
description: 'Write new records to an Airtable table',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
records: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to create, each with a `fields` object',
// Example: [{ fields: { "Field 1": "Value1", "Field 2": "Value2" } }]
},
},
request: {
url: (params) =>
`https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({ records: params.records }),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
records: data.records ?? [],
metadata: {
recordCount: (data.records ?? []).length,
},
},
}
},
outputs: {
records: {
type: 'array',
description: 'Array of created Airtable records',
items: {
type: 'object',
properties: {
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',
properties: {
recordCount: { type: 'number', description: 'Number of records created' },
},
},
},
}
+106
View File
@@ -0,0 +1,106 @@
import type { AirtableDeleteParams, AirtableDeleteResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableDeleteRecordsTool: ToolConfig<AirtableDeleteParams, AirtableDeleteResponse> = {
id: 'airtable_delete_records',
name: 'Airtable Delete Records',
description: 'Delete one or more records from an Airtable table by ID',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
recordIds: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of record IDs to delete (each starts with "rec", e.g., ["recXXXXXXXXXXXXXX"]). Pass a single-element array to delete one record.',
},
},
request: {
url: (params) => {
const base = `https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`
const ids = (params.recordIds ?? [])
.map((id) => (id == null ? '' : String(id).trim()))
.filter(Boolean)
if (ids.length === 0) {
throw new Error('At least one record ID is required to delete')
}
if (ids.length > 10) {
throw new Error(
`Airtable deletes at most 10 records per request (received ${ids.length}). Split the delete into batches of 10 or fewer.`
)
}
const queryParams = new URLSearchParams()
for (const id of ids) {
queryParams.append('records[]', id as string)
}
return `${base}?${queryParams.toString()}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const records = data.records ?? []
return {
success: true,
output: {
records,
metadata: {
recordCount: records.length,
deletedRecordIds: records.map((r: { id: string }) => r.id),
},
},
}
},
outputs: {
records: {
type: 'array',
description: 'Array of deleted Airtable records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Record ID' },
deleted: { type: 'boolean', description: 'Whether the record was deleted' },
},
},
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
recordCount: { type: 'number', description: 'Number of records deleted' },
deletedRecordIds: { type: 'array', description: 'List of deleted record IDs' },
},
},
},
}
+126
View File
@@ -0,0 +1,126 @@
import type { ToolConfig, ToolResponse } from '@/tools/types'
export interface AirtableGetBaseSchemaParams {
accessToken: string
baseId: string
}
interface AirtableFieldSchema {
id: string
name: string
type: string
description?: string
options?: Record<string, unknown>
}
interface AirtableViewSchema {
id: string
name: string
type: string
}
interface AirtableTableSchema {
id: string
name: string
description?: string
fields: AirtableFieldSchema[]
views: AirtableViewSchema[]
}
export interface AirtableGetBaseSchemaResponse extends ToolResponse {
output: {
tables: AirtableTableSchema[]
metadata: {
totalTables: number
}
}
}
export const airtableGetBaseSchemaTool: ToolConfig<
AirtableGetBaseSchemaParams,
AirtableGetBaseSchemaResponse
> = {
id: 'airtable_get_base_schema',
name: 'Airtable Get Base Schema',
description: 'Get the schema of all tables, fields, and views 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}/tables`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const tables = (data.tables || []).map((table: Record<string, unknown>) => ({
id: table.id,
name: table.name,
description: table.description,
fields: ((table.fields as Record<string, unknown>[]) || []).map((field) => ({
id: field.id,
name: field.name,
type: field.type,
description: field.description,
options: field.options,
})),
views: ((table.views as Record<string, unknown>[]) || []).map((view) => ({
id: view.id,
name: view.name,
type: view.type,
})),
}))
return {
success: true,
output: {
tables,
metadata: {
totalTables: tables.length,
},
},
}
},
outputs: {
tables: {
type: 'json',
description: 'Array of table schemas with fields and views',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
description: { type: 'string' },
fields: { type: 'json' },
views: { type: 'json' },
},
},
},
metadata: {
type: 'json',
description: 'Operation metadata including total tables count',
},
},
}
+83
View File
@@ -0,0 +1,83 @@
import type { AirtableGetParams, AirtableGetResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableGetRecordTool: ToolConfig<AirtableGetParams, AirtableGetResponse> = {
id: 'airtable_get_record',
name: 'Airtable Get Record',
description: 'Retrieve a single record from an Airtable table by its ID',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
recordId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Record ID to retrieve (starts with "rec", e.g., "recXXXXXXXXXXXXXX")',
},
},
request: {
url: (params) =>
`https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}/${params.recordId?.trim()}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
record: data, // API returns the single record object
metadata: {
recordCount: 1,
},
},
}
},
outputs: {
record: {
type: 'json',
description: 'Retrieved Airtable record',
properties: {
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',
properties: {
recordCount: { type: 'number', description: 'Number of records returned (always 1)' },
},
},
},
}
+25
View File
@@ -0,0 +1,25 @@
import { airtableCreateRecordsTool } from '@/tools/airtable/create_records'
import { airtableDeleteRecordsTool } from '@/tools/airtable/delete_records'
import { airtableGetBaseSchemaTool } from '@/tools/airtable/get_base_schema'
import { airtableGetRecordTool } from '@/tools/airtable/get_record'
import { airtableListBasesTool } from '@/tools/airtable/list_bases'
import { airtableListRecordsTool } from '@/tools/airtable/list_records'
import { airtableListTablesTool } from '@/tools/airtable/list_tables'
import { airtableUpdateMultipleRecordsTool } from '@/tools/airtable/update_multiple_records'
import { airtableUpdateRecordTool } from '@/tools/airtable/update_record'
import { airtableUpsertRecordsTool } from '@/tools/airtable/upsert_records'
export {
airtableCreateRecordsTool,
airtableDeleteRecordsTool,
airtableGetBaseSchemaTool,
airtableGetRecordTool,
airtableListBasesTool,
airtableListRecordsTool,
airtableListTablesTool,
airtableUpdateMultipleRecordsTool,
airtableUpdateRecordTool,
airtableUpsertRecordsTool,
}
export * from './types'
+91
View File
@@ -0,0 +1,91 @@
import type { AirtableListBasesParams, AirtableListBasesResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableListBasesTool: ToolConfig<AirtableListBasesParams, AirtableListBasesResponse> =
{
id: 'airtable_list_bases',
name: 'Airtable List Bases',
description: 'List all bases the authenticated 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: 'Array of Airtable bases with id, name, and permissionLevel',
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' },
},
},
},
}
+104
View File
@@ -0,0 +1,104 @@
import type { AirtableListParams, AirtableListResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableListRecordsTool: ToolConfig<AirtableListParams, AirtableListResponse> = {
id: 'airtable_list_records',
name: 'Airtable List Records',
description: 'Read records from an Airtable table',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
maxRecords: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of records to return (default: all records)',
},
filterFormula: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Formula to filter records (e.g., "({Field Name} = \'Value\')")',
},
},
request: {
url: (params) => {
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) {
const encodedFormula = params.filterFormula.replace(/'/g, "'")
queryParams.append('filterByFormula', encodedFormula)
}
const queryString = queryParams.toString()
const finalUrl = queryString ? `${url}?${queryString}` : url
return finalUrl
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
records: data.records ?? [],
metadata: {
offset: data.offset ?? null,
totalRecords: (data.records ?? []).length,
},
},
}
},
outputs: {
records: {
type: 'array',
description: 'Array of retrieved Airtable records',
items: {
type: 'object',
properties: {
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 pagination offset and total records count',
properties: {
offset: { type: 'string', description: 'Pagination offset for next page' },
totalRecords: { type: 'number', description: 'Number of records returned' },
},
},
},
}
+124
View File
@@ -0,0 +1,124 @@
import type {
AirtableField,
AirtableListTablesParams,
AirtableListTablesResponse,
AirtableTable,
} from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableListTablesTool: ToolConfig<
AirtableListTablesParams,
AirtableListTablesResponse
> = {
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' },
},
},
},
}
+228
View File
@@ -0,0 +1,228 @@
import type { ToolResponse } from '@/tools/types'
// Common types
interface AirtableRecord {
id: string
createdTime: string
fields: Record<string, any>
}
interface AirtableBase {
id: string
name: string
permissionLevel: 'none' | 'read' | 'comment' | 'edit' | 'create'
}
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[]
}
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
filterFormula?: string
}
export interface AirtableListResponse extends ToolResponse {
output: {
records: AirtableRecord[]
metadata: {
offset?: string
totalRecords: number
}
}
}
// Get Record Types
export interface AirtableGetParams extends AirtableBaseParams {
recordId: string
}
export interface AirtableGetResponse extends ToolResponse {
output: {
record: AirtableRecord
metadata: {
recordCount: 1
}
}
}
// Create Records Types
export interface AirtableCreateParams extends AirtableBaseParams {
records: Array<{ fields: Record<string, any> }>
}
export interface AirtableCreateResponse extends ToolResponse {
output: {
records: AirtableRecord[]
metadata: {
recordCount: number
}
}
}
// Update Record Types (Single)
export interface AirtableUpdateParams extends AirtableBaseParams {
recordId: string
fields: Record<string, any>
}
export interface AirtableUpdateResponse extends ToolResponse {
output: {
record: AirtableRecord // Airtable returns the single updated record
metadata: {
recordCount: 1
updatedFields: string[]
}
}
}
// Update Multiple Records Types
export interface AirtableUpdateMultipleParams extends AirtableBaseParams {
records: Array<{ id: string; fields: Record<string, any> }>
}
export interface AirtableUpdateMultipleResponse extends ToolResponse {
output: {
records: AirtableRecord[] // Airtable returns the array of updated records
metadata: {
recordCount: number
updatedRecordIds: string[]
}
}
}
// Delete Records Types
export interface AirtableDeleteParams extends AirtableBaseParams {
recordIds: string[]
}
interface AirtableDeletedRecord {
id: string
deleted: boolean
}
export interface AirtableDeleteResponse extends ToolResponse {
output: {
records: AirtableDeletedRecord[]
metadata: {
recordCount: number
deletedRecordIds: string[]
}
}
}
// Upsert Records Types
export interface AirtableUpsertParams extends AirtableBaseParams {
records: Array<{ fields: Record<string, any> }>
fieldsToMergeOn: string[]
typecast?: boolean
}
export interface AirtableUpsertResponse extends ToolResponse {
output: {
records: AirtableRecord[]
createdRecords: string[]
updatedRecords: string[]
metadata: {
recordCount: number
createdCount: number
updatedCount: number
}
}
}
export type AirtableResponse =
| AirtableListBasesResponse
| AirtableListTablesResponse
| AirtableListResponse
| AirtableGetResponse
| AirtableCreateResponse
| AirtableUpdateResponse
| AirtableUpdateMultipleResponse
| AirtableDeleteResponse
| AirtableUpsertResponse
| AirtableListBasesResponse
| AirtableGetBaseSchemaResponse
interface AirtableGetBaseSchemaResponse extends ToolResponse {
output: {
tables: Array<{
id: string
name: string
description?: string
fields: Array<{ id: string; name: string; type: string; description?: string }>
views: Array<{ id: string; name: string; type: string }>
}>
metadata: { totalTables: number }
}
}
@@ -0,0 +1,96 @@
import type {
AirtableUpdateMultipleParams,
AirtableUpdateMultipleResponse,
} from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableUpdateMultipleRecordsTool: ToolConfig<
AirtableUpdateMultipleParams,
AirtableUpdateMultipleResponse
> = {
id: 'airtable_update_multiple_records',
name: 'Airtable Update Multiple Records',
description: 'Update multiple existing records in an Airtable table',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
records: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to update, each with an `id` and a `fields` object',
},
},
request: {
url: (params) =>
`https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({ records: params.records }),
},
transformResponse: async (response) => {
const data = await response.json()
const records = data.records ?? []
return {
success: true,
output: {
records,
metadata: {
recordCount: records.length,
updatedRecordIds: records.map((r: { id: string }) => r.id),
},
},
}
},
outputs: {
records: {
type: 'array',
description: 'Array of updated Airtable records',
items: {
type: 'object',
properties: {
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',
properties: {
recordCount: { type: 'number', description: 'Number of records updated' },
updatedRecordIds: { type: 'array', description: 'List of updated record IDs' },
},
},
},
}
+92
View File
@@ -0,0 +1,92 @@
import type { AirtableUpdateParams, AirtableUpdateResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableUpdateRecordTool: ToolConfig<AirtableUpdateParams, AirtableUpdateResponse> = {
id: 'airtable_update_record',
name: 'Airtable Update Record',
description: 'Update an existing record in an Airtable table by ID',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
recordId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Record ID to update (starts with "rec", e.g., "recXXXXXXXXXXXXXX")',
},
fields: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'An object containing the field names and their new values',
},
},
request: {
url: (params) =>
`https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}/${params.recordId?.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({ fields: params.fields }),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
record: data,
metadata: {
recordCount: 1,
updatedFields: Object.keys(data.fields ?? {}),
},
},
}
},
outputs: {
record: {
type: 'json',
description: 'Updated Airtable record',
properties: {
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',
properties: {
recordCount: { type: 'number', description: 'Number of records updated (always 1)' },
updatedFields: { type: 'array', description: 'List of field names that were updated' },
},
},
},
}
+144
View File
@@ -0,0 +1,144 @@
import type { AirtableUpsertParams, AirtableUpsertResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableUpsertRecordsTool: ToolConfig<AirtableUpsertParams, AirtableUpsertResponse> = {
id: 'airtable_upsert_records',
name: 'Airtable Upsert Records',
description:
'Update existing records or create new ones in an Airtable table, matching on the specified merge fields',
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")',
},
tableId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Table ID (starts with "tbl") or table name',
},
records: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to upsert, each with a `fields` object',
},
fieldsToMergeOn: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of field names used to match existing records (max 3). A record is updated when all merge fields match, otherwise it is created. Example: ["Name"]',
},
typecast: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'When true, Airtable automatically converts string values to the field type',
},
},
request: {
url: (params) =>
`https://api.airtable.com/v0/${params.baseId?.trim()}/${params.tableId?.trim()}`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const mergeFields = (params.fieldsToMergeOn ?? [])
.map((f) => (f == null ? '' : String(f).trim()))
.filter(Boolean)
if (mergeFields.length === 0) {
throw new Error('At least one field to merge on is required for upsert')
}
if (mergeFields.length > 3) {
throw new Error(
`Airtable upsert accepts at most 3 fields to merge on (received ${mergeFields.length}).`
)
}
const records = params.records ?? []
if (records.length > 10) {
throw new Error(
`Airtable upserts at most 10 records per request (received ${records.length}). Split the upsert into batches of 10 or fewer.`
)
}
const body: Record<string, unknown> = {
performUpsert: { fieldsToMergeOn: mergeFields },
records,
}
if (params.typecast != null) body.typecast = params.typecast
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const records = data.records ?? []
const createdRecords = data.createdRecords ?? []
const updatedRecords = data.updatedRecords ?? []
return {
success: true,
output: {
records,
createdRecords,
updatedRecords,
metadata: {
recordCount: records.length,
createdCount: createdRecords.length,
updatedCount: updatedRecords.length,
},
},
}
},
outputs: {
records: {
type: 'array',
description: 'Array of upserted Airtable records',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Record ID' },
createdTime: { type: 'string', description: 'Record creation timestamp' },
fields: { type: 'json', description: 'Record field values' },
},
},
},
createdRecords: {
type: 'array',
description: 'IDs of records that were created',
items: { type: 'string', description: 'Created record ID' },
},
updatedRecords: {
type: 'array',
description: 'IDs of records that were updated',
items: { type: 'string', description: 'Updated record ID' },
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
recordCount: { type: 'number', description: 'Total number of records returned' },
createdCount: { type: 'number', description: 'Number of records created' },
updatedCount: { type: 'number', description: 'Number of records updated' },
},
},
},
}