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
+102
View File
@@ -0,0 +1,102 @@
import type {
GoogleAppsheetAddParams,
GoogleAppsheetAddResponse,
} from '@/tools/google_appsheet/types'
import { buildAppsheetActionUrl, readAppsheetResponseBody } from '@/tools/google_appsheet/utils'
import type { ToolConfig } from '@/tools/types'
export const googleAppsheetAddRowsTool: ToolConfig<
GoogleAppsheetAddParams,
GoogleAppsheetAddResponse
> = {
id: 'google_appsheet_add_rows',
name: 'AppSheet Add Rows',
description:
'Add new rows to an AppSheet table. The key column value must be provided explicitly, or omitted when its Initial value expression generates it automatically (e.g. UNIQUEID()).',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AppSheet Application Access Key',
},
appId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'AppSheet app ID (found in App > Settings > Integrations > IN)',
},
tableName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the table to add rows to',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'AppSheet region subdomain: "www" (global, default), "eu", or "asia-southeast"',
},
rows: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of row objects to add, each a column-name/value map, e.g. [{ "FirstName": "Jan", "LastName": "Jones" }]',
},
},
request: {
url: (params) => buildAppsheetActionUrl(params.appId, params.tableName, params.region),
method: 'POST',
headers: (params) => ({
ApplicationAccessKey: params.apiKey,
'Content-Type': 'application/json',
}),
body: (params) => ({
Action: 'Add',
Properties: {},
Rows: params.rows,
}),
},
transformResponse: async (response: Response) => {
const data = await readAppsheetResponseBody(response)
if (!response.ok) {
throw new Error(data.error?.message || data.message || 'Failed to add AppSheet rows')
}
const rows = data.Rows ?? data.rows ?? []
return {
success: true,
output: {
rows,
metadata: {
rowCount: rows.length,
},
},
}
},
outputs: {
rows: {
type: 'array',
description: 'Rows added by AppSheet, including any generated key values',
items: {
type: 'object',
},
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
rowCount: { type: 'number', description: 'Number of rows added' },
},
},
},
}
@@ -0,0 +1,102 @@
import type {
GoogleAppsheetDeleteParams,
GoogleAppsheetDeleteResponse,
} from '@/tools/google_appsheet/types'
import { buildAppsheetActionUrl, readAppsheetResponseBody } from '@/tools/google_appsheet/utils'
import type { ToolConfig } from '@/tools/types'
export const googleAppsheetDeleteRowsTool: ToolConfig<
GoogleAppsheetDeleteParams,
GoogleAppsheetDeleteResponse
> = {
id: 'google_appsheet_delete_rows',
name: 'AppSheet Delete Rows',
description:
'Delete rows from an AppSheet table. Each row only needs to include the key column name and value.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AppSheet Application Access Key',
},
appId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'AppSheet app ID (found in App > Settings > Integrations > IN)',
},
tableName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the table to delete rows from',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'AppSheet region subdomain: "www" (global, default), "eu", or "asia-southeast"',
},
rows: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of row objects identifying rows to delete by key column, e.g. [{ "RowID": "123" }]',
},
},
request: {
url: (params) => buildAppsheetActionUrl(params.appId, params.tableName, params.region),
method: 'POST',
headers: (params) => ({
ApplicationAccessKey: params.apiKey,
'Content-Type': 'application/json',
}),
body: (params) => ({
Action: 'Delete',
Properties: {},
Rows: params.rows,
}),
},
transformResponse: async (response: Response) => {
const data = await readAppsheetResponseBody(response)
if (!response.ok) {
throw new Error(data.error?.message || data.message || 'Failed to delete AppSheet rows')
}
const rows = data.Rows ?? data.rows ?? []
return {
success: true,
output: {
rows,
metadata: {
rowCount: rows.length,
},
},
}
},
outputs: {
rows: {
type: 'array',
description: 'Rows deleted by AppSheet',
items: {
type: 'object',
},
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
rowCount: { type: 'number', description: 'Number of rows deleted' },
},
},
},
}
+102
View File
@@ -0,0 +1,102 @@
import type {
GoogleAppsheetEditParams,
GoogleAppsheetEditResponse,
} from '@/tools/google_appsheet/types'
import { buildAppsheetActionUrl, readAppsheetResponseBody } from '@/tools/google_appsheet/utils'
import type { ToolConfig } from '@/tools/types'
export const googleAppsheetEditRowsTool: ToolConfig<
GoogleAppsheetEditParams,
GoogleAppsheetEditResponse
> = {
id: 'google_appsheet_edit_rows',
name: 'AppSheet Edit Rows',
description:
'Update existing rows in an AppSheet table. Each row must explicitly include the key column name and value, plus any columns to change.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AppSheet Application Access Key',
},
appId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'AppSheet app ID (found in App > Settings > Integrations > IN)',
},
tableName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the table to update rows in',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'AppSheet region subdomain: "www" (global, default), "eu", or "asia-southeast"',
},
rows: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of row objects to update, each including the key column and the columns to change, e.g. [{ "RowID": "123", "Status": "Done" }]',
},
},
request: {
url: (params) => buildAppsheetActionUrl(params.appId, params.tableName, params.region),
method: 'POST',
headers: (params) => ({
ApplicationAccessKey: params.apiKey,
'Content-Type': 'application/json',
}),
body: (params) => ({
Action: 'Edit',
Properties: {},
Rows: params.rows,
}),
},
transformResponse: async (response: Response) => {
const data = await readAppsheetResponseBody(response)
if (!response.ok) {
throw new Error(data.error?.message || data.message || 'Failed to edit AppSheet rows')
}
const rows = data.Rows ?? data.rows ?? []
return {
success: true,
output: {
rows,
metadata: {
rowCount: rows.length,
},
},
}
},
outputs: {
rows: {
type: 'array',
description: 'Rows updated by AppSheet',
items: {
type: 'object',
},
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
rowCount: { type: 'number', description: 'Number of rows updated' },
},
},
},
}
+102
View File
@@ -0,0 +1,102 @@
import type {
GoogleAppsheetFindParams,
GoogleAppsheetFindResponse,
} from '@/tools/google_appsheet/types'
import { buildAppsheetActionUrl, readAppsheetResponseBody } from '@/tools/google_appsheet/utils'
import type { ToolConfig } from '@/tools/types'
export const googleAppsheetFindRowsTool: ToolConfig<
GoogleAppsheetFindParams,
GoogleAppsheetFindResponse
> = {
id: 'google_appsheet_find_rows',
name: 'AppSheet Find Rows',
description:
'Read rows from an AppSheet table. Omit the selector to return every row, or provide a Selector expression (Filter/Select/OrderBy/Top) to narrow and shape the results.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AppSheet Application Access Key',
},
appId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'AppSheet app ID (found in App > Settings > Integrations > IN)',
},
tableName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the table to read from',
},
region: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'AppSheet region subdomain: "www" (global, default), "eu", or "asia-southeast"',
},
selector: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Optional AppSheet expression to filter/sort/limit rows, e.g. Filter(TableName, [Age] >= 21) or Top(OrderBy(Filter(TableName, true), [LastName], true), 10)',
},
},
request: {
url: (params) => buildAppsheetActionUrl(params.appId, params.tableName, params.region),
method: 'POST',
headers: (params) => ({
ApplicationAccessKey: params.apiKey,
'Content-Type': 'application/json',
}),
body: (params) => ({
Action: 'Find',
Properties: params.selector ? { Selector: params.selector } : {},
Rows: [],
}),
},
transformResponse: async (response: Response) => {
const data = await readAppsheetResponseBody(response)
if (!response.ok) {
throw new Error(data.error?.message || data.message || 'Failed to find AppSheet rows')
}
const rows = data.Rows ?? data.rows ?? []
return {
success: true,
output: {
rows,
metadata: {
rowCount: rows.length,
},
},
}
},
outputs: {
rows: {
type: 'array',
description: 'Matching rows returned by AppSheet',
items: {
type: 'object',
},
},
metadata: {
type: 'json',
description: 'Operation metadata',
properties: {
rowCount: { type: 'number', description: 'Number of rows returned' },
},
},
},
}
+13
View File
@@ -0,0 +1,13 @@
import { googleAppsheetAddRowsTool } from '@/tools/google_appsheet/add_rows'
import { googleAppsheetDeleteRowsTool } from '@/tools/google_appsheet/delete_rows'
import { googleAppsheetEditRowsTool } from '@/tools/google_appsheet/edit_rows'
import { googleAppsheetFindRowsTool } from '@/tools/google_appsheet/find_rows'
export {
googleAppsheetAddRowsTool,
googleAppsheetDeleteRowsTool,
googleAppsheetEditRowsTool,
googleAppsheetFindRowsTool,
}
export * from './types'
+72
View File
@@ -0,0 +1,72 @@
import type { ToolResponse } from '@/tools/types'
interface GoogleAppsheetBaseParams {
apiKey: string
appId: string
tableName: string
region?: string
}
export type GoogleAppsheetRow = Record<string, unknown>
// Find Rows Types
export interface GoogleAppsheetFindParams extends GoogleAppsheetBaseParams {
selector?: string
}
export interface GoogleAppsheetFindResponse extends ToolResponse {
output: {
rows: GoogleAppsheetRow[]
metadata: {
rowCount: number
}
}
}
// Add Rows Types
export interface GoogleAppsheetAddParams extends GoogleAppsheetBaseParams {
rows: GoogleAppsheetRow[]
}
export interface GoogleAppsheetAddResponse extends ToolResponse {
output: {
rows: GoogleAppsheetRow[]
metadata: {
rowCount: number
}
}
}
// Edit Rows Types
export interface GoogleAppsheetEditParams extends GoogleAppsheetBaseParams {
rows: GoogleAppsheetRow[]
}
export interface GoogleAppsheetEditResponse extends ToolResponse {
output: {
rows: GoogleAppsheetRow[]
metadata: {
rowCount: number
}
}
}
// Delete Rows Types
export interface GoogleAppsheetDeleteParams extends GoogleAppsheetBaseParams {
rows: GoogleAppsheetRow[]
}
export interface GoogleAppsheetDeleteResponse extends ToolResponse {
output: {
rows: GoogleAppsheetRow[]
metadata: {
rowCount: number
}
}
}
export type GoogleAppsheetResponse =
| GoogleAppsheetFindResponse
| GoogleAppsheetAddResponse
| GoogleAppsheetEditResponse
| GoogleAppsheetDeleteResponse
@@ -0,0 +1,50 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { buildAppsheetActionUrl, readAppsheetResponseBody } from '@/tools/google_appsheet/utils'
describe('buildAppsheetActionUrl', () => {
it('defaults to the global www region when unset', () => {
expect(buildAppsheetActionUrl('app-1', 'Orders')).toBe(
'https://www.appsheet.com/api/v2/apps/app-1/tables/Orders/Action'
)
})
it('builds the URL for a valid non-default region', () => {
expect(buildAppsheetActionUrl('app-1', 'Orders', 'eu')).toBe(
'https://eu.appsheet.com/api/v2/apps/app-1/tables/Orders/Action'
)
})
it('trims and URL-encodes appId and tableName', () => {
expect(buildAppsheetActionUrl(' app 1 ', ' My Table ')).toBe(
'https://www.appsheet.com/api/v2/apps/app%201/tables/My%20Table/Action'
)
})
it('rejects a region outside the known AppSheet regions', () => {
expect(() => buildAppsheetActionUrl('app-1', 'Orders', 'attacker.example.com')).toThrow(
/Invalid AppSheet region/
)
})
})
describe('readAppsheetResponseBody', () => {
it('parses a JSON body', async () => {
const response = new Response('{"Rows":[{"id":"1"}]}')
expect(await readAppsheetResponseBody(response)).toEqual({ Rows: [{ id: '1' }] })
})
it('returns an empty object for an empty body', async () => {
const response = new Response('')
expect(await readAppsheetResponseBody(response)).toEqual({})
})
it('wraps a non-JSON body as a message instead of throwing', async () => {
const response = new Response('<html>502 Bad Gateway</html>')
expect(await readAppsheetResponseBody(response)).toEqual({
message: '<html>502 Bad Gateway</html>',
})
})
})
+36
View File
@@ -0,0 +1,36 @@
const DEFAULT_REGION = 'www'
const ALLOWED_REGIONS = new Set(['www', 'eu', 'asia-southeast'])
/**
* Builds the AppSheet API Action endpoint URL for a given app/table/region.
* Region defaults to the global `www.appsheet.com` domain when unset, and is
* validated against the known AppSheet regions since it is interpolated into
* the request host — an unvalidated value would let a caller redirect the
* Application Access Key to an arbitrary host.
*/
export function buildAppsheetActionUrl(appId: string, tableName: string, region?: string): string {
const trimmedRegion = (region || DEFAULT_REGION).trim()
if (!ALLOWED_REGIONS.has(trimmedRegion)) {
throw new Error(
`Invalid AppSheet region "${trimmedRegion}". Must be one of: ${Array.from(ALLOWED_REGIONS).join(', ')}.`
)
}
const host = `${trimmedRegion}.appsheet.com`
return `https://${host}/api/v2/apps/${encodeURIComponent(appId.trim())}/tables/${encodeURIComponent(tableName.trim())}/Action`
}
/**
* Safely reads an AppSheet API response body. AppSheet does not consistently
* document whether every Action returns a JSON body (e.g. Delete may return an
* empty body on some accounts), so this avoids `response.json()` throwing on
* empty or non-JSON content.
*/
export async function readAppsheetResponseBody(response: Response): Promise<Record<string, any>> {
const text = await response.text()
if (!text) return {}
try {
return JSON.parse(text)
} catch {
return { message: text }
}
}