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
+68
View File
@@ -0,0 +1,68 @@
import type { ConvexFunctionCallParams, ConvexFunctionCallResponse } from '@/tools/convex/types'
import {
convexApiUrl,
convexAuthHeaders,
parseFunctionArgs,
transformFunctionCallResponse,
} from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const actionTool: ToolConfig<ConvexFunctionCallParams, ConvexFunctionCallResponse> = {
id: 'convex_action',
name: 'Convex Run Action',
description: 'Run a Convex action function and return its result',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
functionPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the action function (e.g., emails:send or folder/file:myAction)',
},
args: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Named arguments to pass to the function as a JSON object',
},
},
request: {
url: (params) => convexApiUrl(params.deploymentUrl, '/api/action'),
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
...convexAuthHeaders(params.deployKey),
}),
body: (params) => ({
path: params.functionPath.trim(),
args: parseFunctionArgs(params.args),
format: 'json',
}),
},
transformResponse: async (response: Response) =>
transformFunctionCallResponse(response, 'action'),
outputs: {
value: { type: 'json', description: 'Result returned by the action function' },
logLines: {
type: 'array',
description: 'Log lines printed during the function execution',
items: { type: 'string' },
},
},
}
+92
View File
@@ -0,0 +1,92 @@
import type {
ConvexDocumentDeltasApiResponse,
ConvexDocumentDeltasParams,
ConvexDocumentDeltasResponse,
} from '@/tools/convex/types'
import { convexApiUrl, convexAuthHeaders, parseConvexResponse } from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const documentDeltasTool: ToolConfig<
ConvexDocumentDeltasParams,
ConvexDocumentDeltasResponse
> = {
id: 'convex_document_deltas',
name: 'Convex Document Deltas',
description:
'List documents that changed after a snapshot or previous delta cursor. Deleted documents are returned with a _deleted flag. Requires streaming export, available on Convex paid plans.',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
cursor: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Timestamp cursor to read deltas after. Use the snapshot value from List Documents or the cursor from a previous Document Deltas page.',
},
tableName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Table to read deltas from. Omit to read deltas from all tables.',
},
},
request: {
url: (params) => {
const cursor = String(params.cursor ?? '').trim()
if (!cursor) {
throw new Error(
'Cursor is required: pass the snapshot value from List Documents or the cursor from a previous Document Deltas page'
)
}
const query = new URLSearchParams({ format: 'json', cursor })
if (params.tableName?.trim()) query.set('tableName', params.tableName.trim())
return convexApiUrl(params.deploymentUrl, `/api/document_deltas?${query.toString()}`)
},
method: 'GET',
headers: (params) => convexAuthHeaders(params.deployKey),
},
transformResponse: async (response: Response) => {
const data = (await parseConvexResponse(response)) as ConvexDocumentDeltasApiResponse
return {
success: true,
output: {
documents: data.values ?? [],
hasMore: data.hasMore ?? false,
cursor: data.cursor !== undefined && data.cursor !== null ? String(data.cursor) : null,
},
}
},
outputs: {
documents: {
type: 'array',
description: 'Changed documents, each including _table and _ts fields',
items: { type: 'object' },
},
hasMore: {
type: 'boolean',
description: 'Whether more delta pages remain',
},
cursor: {
type: 'string',
description: 'Cursor to pass back in when fetching the next page of deltas',
optional: true,
},
},
}
+15
View File
@@ -0,0 +1,15 @@
import { actionTool } from '@/tools/convex/action'
import { documentDeltasTool } from '@/tools/convex/document_deltas'
import { listDocumentsTool } from '@/tools/convex/list_documents'
import { listTablesTool } from '@/tools/convex/list_tables'
import { mutationTool } from '@/tools/convex/mutation'
import { queryTool } from '@/tools/convex/query'
import { runFunctionTool } from '@/tools/convex/run_function'
export const convexQueryTool = queryTool
export const convexMutationTool = mutationTool
export const convexActionTool = actionTool
export const convexRunFunctionTool = runFunctionTool
export const convexListTablesTool = listTablesTool
export const convexListDocumentsTool = listDocumentsTool
export const convexDocumentDeltasTool = documentDeltasTool
+103
View File
@@ -0,0 +1,103 @@
import type {
ConvexListDocumentsParams,
ConvexListDocumentsResponse,
ConvexListSnapshotApiResponse,
} from '@/tools/convex/types'
import { convexApiUrl, convexAuthHeaders, parseConvexResponse } from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const listDocumentsTool: ToolConfig<ConvexListDocumentsParams, ConvexListDocumentsResponse> =
{
id: 'convex_list_documents',
name: 'Convex List Documents',
description:
'List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and page cursor back in to fetch the next page. Requires streaming export, available on Convex paid plans.',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
tableName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Table to list documents from. Omit to list documents from all tables.',
},
snapshot: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Snapshot timestamp from a previous page. Omit on the first request to start a new snapshot.',
},
pageCursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Page cursor from a previous page of the same snapshot. Omit on the first request.',
},
},
request: {
url: (params) => {
const query = new URLSearchParams({ format: 'json' })
if (params.tableName?.trim()) query.set('tableName', params.tableName.trim())
const snapshot = String(params.snapshot ?? '').trim()
if (snapshot) query.set('snapshot', snapshot)
const pageCursor = String(params.pageCursor ?? '').trim()
if (pageCursor) query.set('cursor', pageCursor)
return convexApiUrl(params.deploymentUrl, `/api/list_snapshot?${query.toString()}`)
},
method: 'GET',
headers: (params) => convexAuthHeaders(params.deployKey),
},
transformResponse: async (response: Response) => {
const data = (await parseConvexResponse(response)) as ConvexListSnapshotApiResponse
return {
success: true,
output: {
documents: data.values ?? [],
hasMore: data.hasMore ?? false,
snapshot:
data.snapshot !== undefined && data.snapshot !== null ? String(data.snapshot) : null,
pageCursor:
data.cursor !== undefined && data.cursor !== null ? String(data.cursor) : null,
},
}
},
outputs: {
documents: {
type: 'array',
description: 'Documents in this page of the snapshot',
items: { type: 'object' },
},
hasMore: {
type: 'boolean',
description: 'Whether more pages remain in the snapshot',
},
snapshot: {
type: 'string',
description: 'Snapshot timestamp to pass back in when fetching the next page',
optional: true,
},
pageCursor: {
type: 'string',
description: 'Page cursor to pass back in when fetching the next page',
optional: true,
},
},
}
+60
View File
@@ -0,0 +1,60 @@
import type { ConvexListTablesParams, ConvexListTablesResponse } from '@/tools/convex/types'
import { convexApiUrl, convexAuthHeaders, parseConvexResponse } from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const listTablesTool: ToolConfig<ConvexListTablesParams, ConvexListTablesResponse> = {
id: 'convex_list_tables',
name: 'Convex List Tables',
description:
'List all tables in a Convex deployment along with their JSON schemas. Requires streaming export, available on Convex paid plans.',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
},
request: {
url: (params) => convexApiUrl(params.deploymentUrl, '/api/json_schemas?format=json'),
method: 'GET',
headers: (params) => convexAuthHeaders(params.deployKey),
},
transformResponse: async (response: Response) => {
const data = await parseConvexResponse(response)
const schemas =
data !== null && typeof data === 'object' && !Array.isArray(data)
? (data as Record<string, unknown>)
: {}
return {
success: true,
output: {
tables: Object.keys(schemas).sort(),
schemas,
},
}
},
outputs: {
tables: {
type: 'array',
description: 'Names of the tables in the deployment',
items: { type: 'string' },
},
schemas: {
type: 'json',
description: 'Map of table name to the JSON schema of its documents',
},
},
}
+68
View File
@@ -0,0 +1,68 @@
import type { ConvexFunctionCallParams, ConvexFunctionCallResponse } from '@/tools/convex/types'
import {
convexApiUrl,
convexAuthHeaders,
parseFunctionArgs,
transformFunctionCallResponse,
} from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const mutationTool: ToolConfig<ConvexFunctionCallParams, ConvexFunctionCallResponse> = {
id: 'convex_mutation',
name: 'Convex Run Mutation',
description: 'Run a Convex mutation function to write data and return its result',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
functionPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the mutation function (e.g., messages:send or folder/file:myMutation)',
},
args: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Named arguments to pass to the function as a JSON object',
},
},
request: {
url: (params) => convexApiUrl(params.deploymentUrl, '/api/mutation'),
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
...convexAuthHeaders(params.deployKey),
}),
body: (params) => ({
path: params.functionPath.trim(),
args: parseFunctionArgs(params.args),
format: 'json',
}),
},
transformResponse: async (response: Response) =>
transformFunctionCallResponse(response, 'mutation'),
outputs: {
value: { type: 'json', description: 'Result returned by the mutation function' },
logLines: {
type: 'array',
description: 'Log lines printed during the function execution',
items: { type: 'string' },
},
},
}
+67
View File
@@ -0,0 +1,67 @@
import type { ConvexFunctionCallParams, ConvexFunctionCallResponse } from '@/tools/convex/types'
import {
convexApiUrl,
convexAuthHeaders,
parseFunctionArgs,
transformFunctionCallResponse,
} from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const queryTool: ToolConfig<ConvexFunctionCallParams, ConvexFunctionCallResponse> = {
id: 'convex_query',
name: 'Convex Run Query',
description: 'Run a Convex query function and return its result',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
functionPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the query function (e.g., messages:list or folder/file:myQuery)',
},
args: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Named arguments to pass to the function as a JSON object',
},
},
request: {
url: (params) => convexApiUrl(params.deploymentUrl, '/api/query'),
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
...convexAuthHeaders(params.deployKey),
}),
body: (params) => ({
path: params.functionPath.trim(),
args: parseFunctionArgs(params.args),
format: 'json',
}),
},
transformResponse: async (response: Response) => transformFunctionCallResponse(response, 'query'),
outputs: {
value: { type: 'json', description: 'Result returned by the query function' },
logLines: {
type: 'array',
description: 'Log lines printed during the function execution',
items: { type: 'string' },
},
},
}
+76
View File
@@ -0,0 +1,76 @@
import type { ConvexFunctionCallParams, ConvexFunctionCallResponse } from '@/tools/convex/types'
import {
convexApiUrl,
convexAuthHeaders,
parseFunctionArgs,
transformFunctionCallResponse,
} from '@/tools/convex/utils'
import type { ToolConfig } from '@/tools/types'
export const runFunctionTool: ToolConfig<ConvexFunctionCallParams, ConvexFunctionCallResponse> = {
id: 'convex_run_function',
name: 'Convex Run Function',
description:
'Run any Convex function (query, mutation, or action) by path without specifying its type',
version: '1.0.0',
params: {
deploymentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deployment URL (e.g., https://your-deployment.convex.cloud)',
},
deployKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Convex deploy key from the dashboard Settings page',
},
functionPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the function (e.g., messages:list or folder/file:myFunction)',
},
args: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Named arguments to pass to the function as a JSON object',
},
},
request: {
url: (params) => {
const identifier = params.functionPath
.trim()
.replace(':', '/')
.split('/')
.map(encodeURIComponent)
.join('/')
return convexApiUrl(params.deploymentUrl, `/api/run/${identifier}`)
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
...convexAuthHeaders(params.deployKey),
}),
body: (params) => ({
args: parseFunctionArgs(params.args),
format: 'json',
}),
},
transformResponse: async (response: Response) =>
transformFunctionCallResponse(response, 'function'),
outputs: {
value: { type: 'json', description: 'Result returned by the function' },
logLines: {
type: 'array',
description: 'Log lines printed during the function execution',
items: { type: 'string' },
},
},
}
+103
View File
@@ -0,0 +1,103 @@
import type { ToolResponse } from '@/tools/types'
/**
* Shared parameter and response definitions for Convex tools.
* Based on the official Convex HTTP API documentation.
* @see https://docs.convex.dev/http-api/
*/
export interface ConvexBaseParams {
deploymentUrl: string
deployKey: string
}
export interface ConvexFunctionCallParams extends ConvexBaseParams {
functionPath: string
args?: Record<string, unknown> | string
}
export interface ConvexFunctionCallResponse extends ToolResponse {
output: {
value: unknown
logLines: string[]
}
}
export interface ConvexListTablesParams extends ConvexBaseParams {}
export interface ConvexListTablesResponse extends ToolResponse {
output: {
tables: string[]
schemas: Record<string, unknown>
}
}
export interface ConvexListDocumentsParams extends ConvexBaseParams {
tableName?: string
snapshot?: string
pageCursor?: string
}
export interface ConvexListDocumentsResponse extends ToolResponse {
output: {
documents: unknown[]
hasMore: boolean
snapshot: string | null
pageCursor: string | null
}
}
export interface ConvexDocumentDeltasParams extends ConvexBaseParams {
cursor: string
tableName?: string
}
export interface ConvexDocumentDeltasResponse extends ToolResponse {
output: {
documents: unknown[]
hasMore: boolean
cursor: string | null
}
}
/**
* Raw wire shape of Convex function-call responses (`/api/query`, `/api/mutation`,
* `/api/action`, `/api/run/{identifier}`).
* @see https://docs.convex.dev/http-api/#post-apiquery-apimutation-apiaction
*/
export interface ConvexFunctionCallApiResponse {
status?: 'success' | 'error'
value?: unknown
logLines?: string[]
errorMessage?: string
errorData?: unknown
}
/** Raw wire shape of `/api/list_snapshot` responses. */
export interface ConvexListSnapshotApiResponse {
values?: unknown[]
hasMore?: boolean
snapshot?: number | string | null
cursor?: number | string | null
}
/** Raw wire shape of `/api/document_deltas` responses. */
export interface ConvexDocumentDeltasApiResponse {
values?: unknown[]
hasMore?: boolean
cursor?: number | string | null
}
export interface ConvexResponse extends ToolResponse {
output: {
value?: unknown
logLines?: string[]
tables?: string[]
schemas?: Record<string, unknown>
documents?: unknown[]
hasMore?: boolean
snapshot?: string | null
pageCursor?: string | null
cursor?: string | null
}
}
+110
View File
@@ -0,0 +1,110 @@
import { truncate } from '@sim/utils/string'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import type {
ConvexFunctionCallApiResponse,
ConvexFunctionCallResponse,
} from '@/tools/convex/types'
/**
* Builds a Convex deployment API URL from the user-provided deployment URL.
* Accepts URLs with or without a trailing slash.
*
* The deployment URL is validated with the shared SSRF guard so invalid hosts
* fail fast with a clear message; the tool executor additionally re-validates
* with DNS resolution and pins the resolved IP for the actual request.
*/
export function convexApiUrl(deploymentUrl: string, path: string): string {
const trimmed = deploymentUrl.trim().replace(/\/+$/, '')
const validation = validateExternalUrl(trimmed, 'Deployment URL')
if (!validation.isValid) {
throw new Error(`${validation.error} (e.g., https://your-deployment.convex.cloud)`)
}
const parsed = new URL(trimmed)
if (parsed.search || parsed.hash) {
throw new Error(
'Deployment URL must not include a query string or fragment (e.g., https://your-deployment.convex.cloud)'
)
}
return `${trimmed}${path}`
}
/**
* Builds the deployment admin authorization header for Convex HTTP API requests.
* @see https://docs.convex.dev/http-api/#api-authentication
*/
export function convexAuthHeaders(deployKey: string): Record<string, string> {
return { Authorization: `Convex ${deployKey.trim()}` }
}
/**
* Parses function arguments that may arrive as a JSON string or an object.
* Convex function endpoints require a named-argument object.
*/
export function parseFunctionArgs(
args: Record<string, unknown> | string | undefined
): Record<string, unknown> {
if (args === undefined || args === null) return {}
if (typeof args === 'string') {
const trimmed = args.trim()
if (!trimmed) return {}
let parsed: unknown
try {
parsed = JSON.parse(trimmed)
} catch {
throw new Error('Invalid function arguments: expected a JSON object like {"key": "value"}')
}
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('Invalid function arguments: expected a JSON object, not an array or scalar')
}
return parsed as Record<string, unknown>
}
if (typeof args !== 'object' || Array.isArray(args)) {
throw new Error('Invalid function arguments: expected a JSON object, not an array or scalar')
}
return args
}
/**
* Parses a Convex API response body, surfacing non-OK HTTP statuses (e.g. 401
* from an invalid deploy key) as descriptive errors instead of empty results.
*/
export async function parseConvexResponse(response: Response): Promise<unknown> {
if (!response.ok) {
const text = await response.text().catch(() => '')
throw new Error(
`Convex request failed (HTTP ${response.status})${text ? `: ${truncate(text.trim(), 300)}` : ''}`
)
}
return response.json()
}
/**
* Transforms a Convex function-call response. Convex returns HTTP 200 with an
* in-band `status: "error"` payload when the function itself fails, so errors
* must be surfaced here rather than relying on the HTTP status code.
* @see https://docs.convex.dev/http-api/#post-apiquery-apimutation-apiaction
*/
export async function transformFunctionCallResponse(
response: Response,
functionType: 'query' | 'mutation' | 'action' | 'function'
): Promise<ConvexFunctionCallResponse> {
const data = (await parseConvexResponse(response)) as ConvexFunctionCallApiResponse
if (data.status === 'error') {
const details =
data.errorData !== undefined && data.errorData !== null
? ` (${JSON.stringify(data.errorData)})`
: ''
throw new Error(
`Convex ${functionType} failed: ${data.errorMessage || 'Unknown error'}${details}`
)
}
return {
success: true,
output: {
value: data.value ?? null,
logLines: data.logLines ?? [],
},
}
}