From 7861157dcf9c70e0e530953f4a9d802f559e2a87 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 16:29:50 -0700 Subject: [PATCH 1/6] feat(integrations): add Convex integration with function execution and data export tools --- apps/docs/components/icons.tsx | 26 ++ apps/docs/components/ui/icon-mapping.ts | 2 + .../content/docs/en/integrations/convex.mdx | 185 +++++++++++ .../content/docs/en/integrations/meta.json | 1 + apps/sim/blocks/blocks/convex.ts | 291 ++++++++++++++++++ apps/sim/blocks/registry.ts | 3 + apps/sim/components/icons.tsx | 26 ++ apps/sim/lib/integrations/icon-mapping.ts | 2 + apps/sim/lib/integrations/integrations.json | 47 +++ apps/sim/tools/convex/action.ts | 68 ++++ apps/sim/tools/convex/document_deltas.ts | 88 ++++++ apps/sim/tools/convex/index.ts | 15 + apps/sim/tools/convex/list_documents.ts | 97 ++++++ apps/sim/tools/convex/list_tables.ts | 59 ++++ apps/sim/tools/convex/mutation.ts | 68 ++++ apps/sim/tools/convex/query.ts | 67 ++++ apps/sim/tools/convex/run_function.ts | 76 +++++ apps/sim/tools/convex/types.ts | 74 +++++ apps/sim/tools/convex/utils.ts | 79 +++++ apps/sim/tools/registry.ts | 16 + 20 files changed, 1290 insertions(+) create mode 100644 apps/docs/content/docs/en/integrations/convex.mdx create mode 100644 apps/sim/blocks/blocks/convex.ts create mode 100644 apps/sim/tools/convex/action.ts create mode 100644 apps/sim/tools/convex/document_deltas.ts create mode 100644 apps/sim/tools/convex/index.ts create mode 100644 apps/sim/tools/convex/list_documents.ts create mode 100644 apps/sim/tools/convex/list_tables.ts create mode 100644 apps/sim/tools/convex/mutation.ts create mode 100644 apps/sim/tools/convex/query.ts create mode 100644 apps/sim/tools/convex/run_function.ts create mode 100644 apps/sim/tools/convex/types.ts create mode 100644 apps/sim/tools/convex/utils.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6161069bef..ad217d5182 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2056,6 +2056,32 @@ export function ConfluenceIcon(props: SVGProps) { ) } +export function ConvexIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function TwilioIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index ebf24abb50..3b03a5909b 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -37,6 +37,7 @@ import { CloudWatchIcon, CodePipelineIcon, ConfluenceIcon, + ConvexIcon, CrowdStrikeIcon, CursorIcon, DagsterIcon, @@ -257,6 +258,7 @@ export const blockTypeToIconMap: Record = { codepipeline: CodePipelineIcon, confluence: ConfluenceIcon, confluence_v2: ConfluenceIcon, + convex: ConvexIcon, crowdstrike: CrowdStrikeIcon, cursor: CursorIcon, cursor_v2: CursorIcon, diff --git a/apps/docs/content/docs/en/integrations/convex.mdx b/apps/docs/content/docs/en/integrations/convex.mdx new file mode 100644 index 0000000000..db9b62f3e9 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/convex.mdx @@ -0,0 +1,185 @@ +--- +title: Convex +description: Use Convex database +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Convex](https://www.convex.dev/) is an open-source reactive backend platform that combines a document database, serverless functions, and real-time sync in one developer-friendly package. Instead of writing SQL, you define queries, mutations, and actions in TypeScript that run right next to your data, and every client subscribed to a query updates automatically when the underlying data changes. + +**Why Convex?** + +- **Functions as the API:** Queries (reads), mutations (transactional writes), and actions (side effects like calling external APIs) are the building blocks of your backend — typed, versioned, and deployed together. +- **Reactive by default:** Query results update live as data changes, with no cache invalidation or polling logic to maintain. +- **Transactional writes:** Mutations run as ACID transactions with serializable isolation, so your data stays consistent without manual locking. +- **Built-in schema awareness:** Convex tracks the shape of every table, so tooling can introspect your data model without a separate migration system. + +**Using Convex in Sim** + +Sim's Convex integration connects your workflows to any Convex deployment with two fields: the deployment URL and a deploy key from the dashboard Settings page. From there you can: + +- **Run functions:** Call query, mutation, and action functions with named JSON arguments — or use Run Function when you don't want to specify the function type. +- **Inspect your data model:** List Tables returns every table in the deployment with the JSON schema of its documents. +- **Export and sync data:** List Documents pages through a consistent snapshot of a table, and Document Deltas returns only the documents that changed since a snapshot — including deletions — making incremental syncs to warehouses, search indexes, or other tools straightforward. + +Typical patterns include agents that read and write application data through your existing Convex functions, scheduled exports to analytics destinations, and change-driven automations that react to new or updated documents. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Convex into the workflow. Run query, mutation, and action functions on your deployment, list tables with their schemas, and export documents with snapshot pagination and change deltas. + + + +## Actions + +### `convex_query` + +Run a Convex query function and return its result + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `functionPath` | string | Yes | Path to the query function \(e.g., messages:list or folder/file:myQuery\) | +| `args` | json | No | Named arguments to pass to the function as a JSON object | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `value` | json | Result returned by the query function | +| `logLines` | array | Log lines printed during the function execution | + +### `convex_mutation` + +Run a Convex mutation function to write data and return its result + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `functionPath` | string | Yes | Path to the mutation function \(e.g., messages:send or folder/file:myMutation\) | +| `args` | json | No | Named arguments to pass to the function as a JSON object | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `value` | json | Result returned by the mutation function | +| `logLines` | array | Log lines printed during the function execution | + +### `convex_action` + +Run a Convex action function and return its result + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `functionPath` | string | Yes | Path to the action function \(e.g., emails:send or folder/file:myAction\) | +| `args` | json | No | Named arguments to pass to the function as a JSON object | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `value` | json | Result returned by the action function | +| `logLines` | array | Log lines printed during the function execution | + +### `convex_run_function` + +Run any Convex function (query, mutation, or action) by path without specifying its type + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `functionPath` | string | Yes | Path to the function \(e.g., messages:list or folder/file:myFunction\) | +| `args` | json | No | Named arguments to pass to the function as a JSON object | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `value` | json | Result returned by the function | +| `logLines` | array | Log lines printed during the function execution | + +### `convex_list_tables` + +List all tables in a Convex deployment along with their JSON schemas + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tables` | array | Names of the tables in the deployment | +| `schemas` | json | Map of table name to the JSON schema of its documents | + +### `convex_list_documents` + +List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and cursor back in to fetch the next page. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `tableName` | string | No | Table to list documents from. Omit to list documents from all tables. | +| `snapshot` | string | No | Snapshot timestamp from a previous page. Omit on the first request to start a new snapshot. | +| `cursor` | string | No | Pagination cursor from a previous page. Omit on the first request. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `documents` | array | Documents in this page of the snapshot | +| `hasMore` | boolean | Whether more pages remain in the snapshot | +| `snapshot` | string | Snapshot timestamp to pass back in when fetching the next page | +| `cursor` | string | Pagination cursor to pass back in when fetching the next page | + +### `convex_document_deltas` + +List documents that changed after a snapshot or previous delta cursor. Deleted documents are returned with a _deleted flag. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `deploymentUrl` | string | Yes | Convex deployment URL \(e.g., https://your-deployment.convex.cloud\) | +| `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | +| `cursor` | string | Yes | Timestamp cursor to read deltas after. Use the snapshot value from List Documents or the cursor from a previous Document Deltas page. | +| `tableName` | string | No | Table to read deltas from. Omit to read deltas from all tables. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `documents` | array | Changed documents, each including _table and _ts fields | +| `hasMore` | boolean | Whether more delta pages remain | +| `cursor` | string | Cursor to pass back in when fetching the next page of deltas | + + diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index d96c79dba7..8043c5157e 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -34,6 +34,7 @@ "cloudwatch", "codepipeline", "confluence", + "convex", "crowdstrike", "cursor", "dagster", diff --git a/apps/sim/blocks/blocks/convex.ts b/apps/sim/blocks/blocks/convex.ts new file mode 100644 index 0000000000..81d542d898 --- /dev/null +++ b/apps/sim/blocks/blocks/convex.ts @@ -0,0 +1,291 @@ +import { ConvexIcon } from '@/components/icons' +import { AuthMode, type BlockConfig, type BlockMeta, IntegrationType } from '@/blocks/types' +import type { ConvexResponse } from '@/tools/convex/types' + +export const ConvexBlock: BlockConfig = { + type: 'convex', + name: 'Convex', + description: 'Use Convex database', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Convex into the workflow. Run query, mutation, and action functions on your deployment, list tables with their schemas, and export documents with snapshot pagination and change deltas.', + docsLink: 'https://docs.sim.ai/integrations/convex', + category: 'tools', + integrationType: IntegrationType.Databases, + bgColor: '#FFFFFF', + icon: ConvexIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Run Query', id: 'query' }, + { label: 'Run Mutation', id: 'mutation' }, + { label: 'Run Action', id: 'action' }, + { label: 'Run Function', id: 'run_function' }, + { label: 'List Tables', id: 'list_tables' }, + { label: 'List Documents', id: 'list_documents' }, + { label: 'Document Deltas', id: 'document_deltas' }, + ], + value: () => 'query', + }, + { + id: 'deploymentUrl', + title: 'Deployment URL', + type: 'short-input', + placeholder: 'https://your-deployment.convex.cloud', + required: true, + }, + { + id: 'deployKey', + title: 'Deploy Key', + type: 'short-input', + placeholder: 'Your Convex deploy key', + password: true, + required: true, + }, + { + id: 'functionPath', + title: 'Function Path', + type: 'short-input', + placeholder: 'messages:list', + condition: { field: 'operation', value: ['query', 'mutation', 'action', 'run_function'] }, + required: true, + }, + { + id: 'args', + title: 'Function Arguments', + type: 'code', + placeholder: '{\n "key": "value"\n}', + condition: { field: 'operation', value: ['query', 'mutation', 'action', 'run_function'] }, + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `Generate a JSON object of named arguments for a Convex function based on the user's request. + +### CONTEXT +{context} + +### RULES +- Convex functions take a single object of named arguments +- Keys must match the argument names declared by the function +- Values must be JSON-serializable (strings, numbers, booleans, arrays, objects, null) + +### EXAMPLES +"send a message saying hello from sim" -> {"body": "hello from sim", "author": "sim"} +"list the 10 most recent items" -> {"limit": 10} + +Return ONLY the JSON object - no explanations, no markdown, no extra text.`, + placeholder: 'Describe the arguments to pass...', + generationType: 'json-object', + }, + }, + { + id: 'tableName', + title: 'Table', + type: 'short-input', + placeholder: 'Table name (leave empty for all tables)', + condition: { field: 'operation', value: ['list_documents', 'document_deltas'] }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Snapshot value from List Documents or cursor from a previous page', + condition: { field: 'operation', value: 'document_deltas' }, + required: true, + }, + { + id: 'snapshot', + title: 'Snapshot', + type: 'short-input', + mode: 'advanced', + placeholder: 'Snapshot timestamp from a previous page', + condition: { field: 'operation', value: 'list_documents' }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + mode: 'advanced', + placeholder: 'Cursor from a previous page', + condition: { field: 'operation', value: 'list_documents' }, + }, + ], + tools: { + access: [ + 'convex_query', + 'convex_mutation', + 'convex_action', + 'convex_run_function', + 'convex_list_tables', + 'convex_list_documents', + 'convex_document_deltas', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'query': + return 'convex_query' + case 'mutation': + return 'convex_mutation' + case 'action': + return 'convex_action' + case 'run_function': + return 'convex_run_function' + case 'list_tables': + return 'convex_list_tables' + case 'list_documents': + return 'convex_list_documents' + case 'document_deltas': + return 'convex_document_deltas' + default: + throw new Error(`Invalid Convex operation: ${params.operation}`) + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + deploymentUrl: { type: 'string', description: 'Convex deployment URL' }, + deployKey: { type: 'string', description: 'Convex deploy key' }, + functionPath: { type: 'string', description: 'Function path (e.g., messages:list)' }, + args: { type: 'json', description: 'Named arguments for the function' }, + tableName: { type: 'string', description: 'Table to read from (empty for all tables)' }, + snapshot: { type: 'string', description: 'Snapshot timestamp for pagination' }, + cursor: { type: 'string', description: 'Pagination cursor' }, + }, + outputs: { + value: { + type: 'json', + description: 'Result returned by the query, mutation, or action function', + }, + logLines: { + type: 'array', + description: 'Log lines printed during the function execution', + }, + tables: { + type: 'array', + description: 'Names of the tables in the deployment', + }, + schemas: { + type: 'json', + description: 'Map of table name to the JSON schema of its documents', + }, + documents: { + type: 'array', + description: 'Documents returned by List Documents or Document Deltas', + }, + hasMore: { + type: 'boolean', + description: 'Whether more pages remain', + }, + snapshot: { + type: 'string', + description: 'Snapshot timestamp to pass back in for the next page', + }, + cursor: { + type: 'string', + description: 'Cursor to pass back in for the next page', + }, + }, +} + +export const ConvexBlockMeta = { + tags: ['cloud'], + templates: [ + { + icon: ConvexIcon, + title: 'Convex support ticket triage', + prompt: + 'Build a workflow that runs a Convex query to fetch open support tickets, classifies each by urgency with an agent, writes the triage label back via a Convex mutation, and posts critical tickets to Slack.', + modules: ['agent', 'workflows'], + category: 'support', + tags: ['automation', 'customer-support'], + alsoIntegrations: ['slack'], + }, + { + icon: ConvexIcon, + title: 'Convex nightly backup to S3', + prompt: + 'Create a scheduled workflow that runs each night, pages through every Convex table with List Documents, writes the exported JSON to S3 with date partitions, and records the run in an audit table.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['devops', 'sync'], + alsoIntegrations: ['s3'], + }, + { + icon: ConvexIcon, + title: 'Convex change-data alerting', + prompt: + 'Build a scheduled workflow that polls Convex Document Deltas for changed rows since the last run, filters for high-value records like fraud flags or large orders, and posts an alert with context to Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['monitoring', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: ConvexIcon, + title: 'Convex user onboarding automation', + prompt: + 'Create a workflow that receives new-signup webhooks, runs a Convex mutation to provision the user record with defaults, and sends a personalized welcome email.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['automation', 'email'], + alsoIntegrations: ['gmail'], + }, + { + icon: ConvexIcon, + title: 'Convex daily metrics digest', + prompt: + 'Create a scheduled daily workflow that runs Convex queries for new signups, active users, and key feature usage, summarizes the numbers with an agent, and posts a digest to Slack with day-over-day trend.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'productivity', + tags: ['reporting', 'product'], + alsoIntegrations: ['slack'], + }, + { + icon: ConvexIcon, + title: 'Convex search index sync', + prompt: + 'Build a scheduled workflow that uses Convex Document Deltas to mirror changed documents into an Algolia index, removes deleted documents, and writes sync lag to a tables-based monitor.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'engineering', + tags: ['engineering', 'sync'], + alsoIntegrations: ['algolia'], + }, + { + icon: ConvexIcon, + title: 'Convex schema drift monitor', + prompt: + 'Create a scheduled workflow that runs Convex List Tables, diffs the returned table schemas against the last snapshot stored in a table, and notifies the engineering channel when fields are added, removed, or change type.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'engineering', + tags: ['engineering', 'monitoring'], + alsoIntegrations: ['slack'], + }, + ], + skills: [ + { + name: 'run-convex-function', + description: + 'Run a Convex query, mutation, or action with named arguments and use its result.', + content: + '# Run a Convex Function\n\nCall a function deployed to Convex and work with its return value.\n\n## Steps\n1. Pick the operation that matches the function type: Run Query for reads, Run Mutation for writes, Run Action for side effects like calling external APIs.\n2. Provide the Deployment URL (https://your-deployment.convex.cloud) and a deploy key from the dashboard Settings page.\n3. Set Function Path in module:function form, for example messages:list or tasks/admin:reset.\n4. Pass Function Arguments as a JSON object whose keys match the argument names the function declares, for example {"limit": 10}.\n\n## Output\nThe function result is available as value, with any console output in logLines. Surface the fields downstream steps need.', + }, + { + name: 'export-convex-table', + description: + 'Page through a full Convex table snapshot with List Documents until hasMore is false.', + content: + '# Export a Convex Table\n\nRead every document in a table using snapshot pagination so the export is consistent.\n\n## Steps\n1. Use the List Documents operation with the deployment URL, deploy key, and the table name (leave empty to export all tables).\n2. On the first call leave Snapshot and Cursor empty; the response pins a snapshot timestamp.\n3. While hasMore is true, call List Documents again passing back the returned snapshot and cursor values.\n4. Collect the documents arrays from each page into your destination.\n\n## Output\nA complete, point-in-time set of documents for the table, each including _id and _creationTime.', + }, + { + name: 'sync-convex-changes', + description: 'Fetch only changed Convex documents since a snapshot using Document Deltas.', + content: + '# Sync Convex Changes Incrementally\n\nAfter an initial export, keep a downstream copy fresh by reading only what changed.\n\n## Steps\n1. Run an initial export with List Documents and keep the final snapshot value.\n2. On each sync run, call Document Deltas with that value as the Cursor (and optionally a table name).\n3. While hasMore is true, keep calling Document Deltas with the returned cursor; persist the last cursor for the next run.\n4. Apply each document by _id; documents with _deleted set to true should be removed downstream.\n\n## Output\nThe changed documents since the stored cursor plus a new cursor to persist, giving exactly-once incremental sync.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index bc7d8b42bb..e538c6753e 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -37,6 +37,7 @@ import { CloudWatchBlock, CloudWatchBlockMeta } from '@/blocks/blocks/cloudwatch import { CodePipelineBlock, CodePipelineBlockMeta } from '@/blocks/blocks/codepipeline' import { ConditionBlock } from '@/blocks/blocks/condition' import { ConfluenceBlock, ConfluenceBlockMeta, ConfluenceV2Block } from '@/blocks/blocks/confluence' +import { ConvexBlock, ConvexBlockMeta } from '@/blocks/blocks/convex' import { CredentialBlock } from '@/blocks/blocks/credential' import { CrowdStrikeBlock, CrowdStrikeBlockMeta } from '@/blocks/blocks/crowdstrike' import { CursorBlock, CursorBlockMeta, CursorV2Block } from '@/blocks/blocks/cursor' @@ -361,6 +362,7 @@ const BLOCK_REGISTRY: Record = { condition: ConditionBlock, confluence: ConfluenceBlock, confluence_v2: ConfluenceV2Block, + convex: ConvexBlock, credential: CredentialBlock, crowdstrike: CrowdStrikeBlock, cursor: CursorBlock, @@ -654,6 +656,7 @@ const BLOCK_META_REGISTRY: Record = { cloudwatch: CloudWatchBlockMeta, codepipeline: CodePipelineBlockMeta, confluence: ConfluenceBlockMeta, + convex: ConvexBlockMeta, crowdstrike: CrowdStrikeBlockMeta, cursor: CursorBlockMeta, dagster: DagsterBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6161069bef..ad217d5182 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2056,6 +2056,32 @@ export function ConfluenceIcon(props: SVGProps) { ) } +export function ConvexIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function TwilioIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 7344cb8ee1..e627b9bbc8 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -37,6 +37,7 @@ import { CloudWatchIcon, CodePipelineIcon, ConfluenceIcon, + ConvexIcon, CrowdStrikeIcon, CursorIcon, DagsterIcon, @@ -254,6 +255,7 @@ export const blockTypeToIconMap: Record = { cloudwatch: CloudWatchIcon, codepipeline: CodePipelineIcon, confluence_v2: ConfluenceIcon, + convex: ConvexIcon, crowdstrike: CrowdStrikeIcon, cursor_v2: CursorIcon, dagster: DagsterIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 72301855dd..4cb3d61823 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -3196,6 +3196,53 @@ "integrationType": "documents", "tags": ["knowledge-base", "content-management", "note-taking"] }, + { + "type": "convex", + "slug": "convex", + "name": "Convex", + "description": "Use Convex database", + "longDescription": "Integrate Convex into the workflow. Run query, mutation, and action functions on your deployment, list tables with their schemas, and export documents with snapshot pagination and change deltas.", + "bgColor": "#FFFFFF", + "iconName": "ConvexIcon", + "docsUrl": "https://docs.sim.ai/integrations/convex", + "operations": [ + { + "name": "Run Query", + "description": "Run a Convex query function and return its result" + }, + { + "name": "Run Mutation", + "description": "Run a Convex mutation function to write data and return its result" + }, + { + "name": "Run Action", + "description": "Run a Convex action function and return its result" + }, + { + "name": "Run Function", + "description": "Run any Convex function (query, mutation, or action) by path without specifying its type" + }, + { + "name": "List Tables", + "description": "List all tables in a Convex deployment along with their JSON schemas" + }, + { + "name": "List Documents", + "description": "List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and cursor back in to fetch the next page." + }, + { + "name": "Document Deltas", + "description": "List documents that changed after a snapshot or previous delta cursor. Deleted documents are returned with a _deleted flag." + } + ], + "operationCount": 7, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "databases", + "tags": ["cloud"] + }, { "type": "crowdstrike", "slug": "crowdstrike", diff --git a/apps/sim/tools/convex/action.ts b/apps/sim/tools/convex/action.ts new file mode 100644 index 0000000000..3d0e3ac472 --- /dev/null +++ b/apps/sim/tools/convex/action.ts @@ -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 = { + 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' }, + }, + }, +} diff --git a/apps/sim/tools/convex/document_deltas.ts b/apps/sim/tools/convex/document_deltas.ts new file mode 100644 index 0000000000..5888161a49 --- /dev/null +++ b/apps/sim/tools/convex/document_deltas.ts @@ -0,0 +1,88 @@ +import type { ConvexDocumentDeltasParams, ConvexDocumentDeltasResponse } from '@/tools/convex/types' +import { convexApiUrl, convexAuthHeaders } 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.', + 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 response.json() + + 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, + }, + }, +} diff --git a/apps/sim/tools/convex/index.ts b/apps/sim/tools/convex/index.ts new file mode 100644 index 0000000000..3ea3cd86a7 --- /dev/null +++ b/apps/sim/tools/convex/index.ts @@ -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 diff --git a/apps/sim/tools/convex/list_documents.ts b/apps/sim/tools/convex/list_documents.ts new file mode 100644 index 0000000000..24f018b677 --- /dev/null +++ b/apps/sim/tools/convex/list_documents.ts @@ -0,0 +1,97 @@ +import type { ConvexListDocumentsParams, ConvexListDocumentsResponse } from '@/tools/convex/types' +import { convexApiUrl, convexAuthHeaders } from '@/tools/convex/utils' +import type { ToolConfig } from '@/tools/types' + +export const listDocumentsTool: ToolConfig = + { + id: 'convex_list_documents', + name: 'Convex List Documents', + description: + 'List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and cursor back in to fetch the next page.', + 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.', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor from a previous page. 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 cursor = String(params.cursor ?? '').trim() + if (cursor) query.set('cursor', cursor) + return convexApiUrl(params.deploymentUrl, `/api/list_snapshot?${query.toString()}`) + }, + method: 'GET', + headers: (params) => convexAuthHeaders(params.deployKey), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + documents: data.values ?? [], + hasMore: data.hasMore ?? false, + snapshot: + data.snapshot !== undefined && data.snapshot !== null ? String(data.snapshot) : null, + cursor: 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, + }, + cursor: { + type: 'string', + description: 'Pagination cursor to pass back in when fetching the next page', + optional: true, + }, + }, + } diff --git a/apps/sim/tools/convex/list_tables.ts b/apps/sim/tools/convex/list_tables.ts new file mode 100644 index 0000000000..dc66105df0 --- /dev/null +++ b/apps/sim/tools/convex/list_tables.ts @@ -0,0 +1,59 @@ +import type { ConvexListTablesParams, ConvexListTablesResponse } from '@/tools/convex/types' +import { convexApiUrl, convexAuthHeaders } from '@/tools/convex/utils' +import type { ToolConfig } from '@/tools/types' + +export const listTablesTool: ToolConfig = { + id: 'convex_list_tables', + name: 'Convex List Tables', + description: 'List all tables in a Convex deployment along with their JSON schemas', + 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 response.json() + const schemas = + data !== null && typeof data === 'object' && !Array.isArray(data) + ? (data as Record) + : {} + + 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', + }, + }, +} diff --git a/apps/sim/tools/convex/mutation.ts b/apps/sim/tools/convex/mutation.ts new file mode 100644 index 0000000000..1bf1d6e0a3 --- /dev/null +++ b/apps/sim/tools/convex/mutation.ts @@ -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 = { + 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' }, + }, + }, +} diff --git a/apps/sim/tools/convex/query.ts b/apps/sim/tools/convex/query.ts new file mode 100644 index 0000000000..9a3873f388 --- /dev/null +++ b/apps/sim/tools/convex/query.ts @@ -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 = { + 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' }, + }, + }, +} diff --git a/apps/sim/tools/convex/run_function.ts b/apps/sim/tools/convex/run_function.ts new file mode 100644 index 0000000000..6da50fd464 --- /dev/null +++ b/apps/sim/tools/convex/run_function.ts @@ -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 = { + 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' }, + }, + }, +} diff --git a/apps/sim/tools/convex/types.ts b/apps/sim/tools/convex/types.ts new file mode 100644 index 0000000000..a9a7d0b26c --- /dev/null +++ b/apps/sim/tools/convex/types.ts @@ -0,0 +1,74 @@ +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 +} + +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 + } +} + +export interface ConvexListDocumentsParams extends ConvexBaseParams { + tableName?: string + snapshot?: string + cursor?: string +} + +export interface ConvexListDocumentsResponse extends ToolResponse { + output: { + documents: unknown[] + hasMore: boolean + snapshot: string | null + cursor: string | null + } +} + +export interface ConvexDocumentDeltasParams extends ConvexBaseParams { + cursor: string + tableName?: string +} + +export interface ConvexDocumentDeltasResponse extends ToolResponse { + output: { + documents: unknown[] + hasMore: boolean + cursor: string | null + } +} + +export interface ConvexResponse extends ToolResponse { + output: { + value?: unknown + logLines?: string[] + tables?: string[] + schemas?: Record + documents?: unknown[] + hasMore?: boolean + snapshot?: string | null + cursor?: string | null + } +} diff --git a/apps/sim/tools/convex/utils.ts b/apps/sim/tools/convex/utils.ts new file mode 100644 index 0000000000..63596c107a --- /dev/null +++ b/apps/sim/tools/convex/utils.ts @@ -0,0 +1,79 @@ +import type { 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. + */ +export function convexApiUrl(deploymentUrl: string, path: string): string { + const trimmed = deploymentUrl.trim().replace(/\/+$/, '') + if (!/^https?:\/\//.test(trimmed)) { + throw new Error( + 'Deployment URL must start with https:// (e.g., https://your-deployment.convex.cloud) or http:// for self-hosted deployments' + ) + } + 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 { + 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 | undefined +): Record { + 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 + } + return args +} + +/** + * 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 { + const data = await response.json() + + 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 ?? [], + }, + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 2923847b02..2190d2e4e9 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -492,6 +492,15 @@ import { confluenceUpdateTool, confluenceUploadAttachmentTool, } from '@/tools/confluence' +import { + convexActionTool, + convexDocumentDeltasTool, + convexListDocumentsTool, + convexListTablesTool, + convexMutationTool, + convexQueryTool, + convexRunFunctionTool, +} from '@/tools/convex' import { crowdstrikeGetSensorAggregatesTool, crowdstrikeGetSensorDetailsTool, @@ -4422,6 +4431,13 @@ export const tools: Record = { codepipeline_retry_stage_execution: codepipelineRetryStageExecutionTool, codepipeline_start_execution: codepipelineStartExecutionTool, codepipeline_stop_execution: codepipelineStopExecutionTool, + convex_query: convexQueryTool, + convex_mutation: convexMutationTool, + convex_action: convexActionTool, + convex_run_function: convexRunFunctionTool, + convex_list_tables: convexListTablesTool, + convex_list_documents: convexListDocumentsTool, + convex_document_deltas: convexDocumentDeltasTool, crowdstrike_get_sensor_aggregates: crowdstrikeGetSensorAggregatesTool, crowdstrike_get_sensor_details: crowdstrikeGetSensorDetailsTool, crowdstrike_query_sensors: crowdstrikeQuerySensorsTool, From b82f61d2e9e7dcc90581f3236264805afdc4e8e7 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 16:39:03 -0700 Subject: [PATCH 2/6] fix(convex): separate List Documents page cursor from deltas cursor and surface HTTP errors in transforms --- apps/sim/blocks/blocks/convex.ts | 16 +++++++++++--- apps/sim/tools/convex/document_deltas.ts | 10 ++++++--- apps/sim/tools/convex/list_documents.ts | 10 ++++++--- apps/sim/tools/convex/list_tables.ts | 4 ++-- apps/sim/tools/convex/types.ts | 28 ++++++++++++++++++++++++ apps/sim/tools/convex/utils.ts | 22 +++++++++++++++++-- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/apps/sim/blocks/blocks/convex.ts b/apps/sim/blocks/blocks/convex.ts index 81d542d898..f3a1e0bb38 100644 --- a/apps/sim/blocks/blocks/convex.ts +++ b/apps/sim/blocks/blocks/convex.ts @@ -105,7 +105,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, condition: { field: 'operation', value: 'list_documents' }, }, { - id: 'cursor', + id: 'pageCursor', title: 'Cursor', type: 'short-input', mode: 'advanced', @@ -144,6 +144,15 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, throw new Error(`Invalid Convex operation: ${params.operation}`) } }, + params: (params) => { + const { pageCursor, ...rest } = params + + if (params.operation === 'list_documents') { + rest.cursor = pageCursor + } + + return rest + }, }, }, inputs: { @@ -153,8 +162,9 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, functionPath: { type: 'string', description: 'Function path (e.g., messages:list)' }, args: { type: 'json', description: 'Named arguments for the function' }, tableName: { type: 'string', description: 'Table to read from (empty for all tables)' }, - snapshot: { type: 'string', description: 'Snapshot timestamp for pagination' }, - cursor: { type: 'string', description: 'Pagination cursor' }, + snapshot: { type: 'string', description: 'Snapshot timestamp for List Documents pagination' }, + cursor: { type: 'string', description: 'Timestamp cursor for Document Deltas' }, + pageCursor: { type: 'string', description: 'Pagination cursor for List Documents' }, }, outputs: { value: { diff --git a/apps/sim/tools/convex/document_deltas.ts b/apps/sim/tools/convex/document_deltas.ts index 5888161a49..2be0ed72b2 100644 --- a/apps/sim/tools/convex/document_deltas.ts +++ b/apps/sim/tools/convex/document_deltas.ts @@ -1,5 +1,9 @@ -import type { ConvexDocumentDeltasParams, ConvexDocumentDeltasResponse } from '@/tools/convex/types' -import { convexApiUrl, convexAuthHeaders } from '@/tools/convex/utils' +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< @@ -57,7 +61,7 @@ export const documentDeltasTool: ToolConfig< }, transformResponse: async (response: Response) => { - const data = await response.json() + const data = (await parseConvexResponse(response)) as ConvexDocumentDeltasApiResponse return { success: true, diff --git a/apps/sim/tools/convex/list_documents.ts b/apps/sim/tools/convex/list_documents.ts index 24f018b677..523b415d64 100644 --- a/apps/sim/tools/convex/list_documents.ts +++ b/apps/sim/tools/convex/list_documents.ts @@ -1,5 +1,9 @@ -import type { ConvexListDocumentsParams, ConvexListDocumentsResponse } from '@/tools/convex/types' -import { convexApiUrl, convexAuthHeaders } from '@/tools/convex/utils' +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 = @@ -59,7 +63,7 @@ export const listDocumentsTool: ToolConfig { - const data = await response.json() + const data = (await parseConvexResponse(response)) as ConvexListSnapshotApiResponse return { success: true, diff --git a/apps/sim/tools/convex/list_tables.ts b/apps/sim/tools/convex/list_tables.ts index dc66105df0..308934b240 100644 --- a/apps/sim/tools/convex/list_tables.ts +++ b/apps/sim/tools/convex/list_tables.ts @@ -1,5 +1,5 @@ import type { ConvexListTablesParams, ConvexListTablesResponse } from '@/tools/convex/types' -import { convexApiUrl, convexAuthHeaders } from '@/tools/convex/utils' +import { convexApiUrl, convexAuthHeaders, parseConvexResponse } from '@/tools/convex/utils' import type { ToolConfig } from '@/tools/types' export const listTablesTool: ToolConfig = { @@ -30,7 +30,7 @@ export const listTablesTool: ToolConfig { - const data = await response.json() + const data = await parseConvexResponse(response) const schemas = data !== null && typeof data === 'object' && !Array.isArray(data) ? (data as Record) diff --git a/apps/sim/tools/convex/types.ts b/apps/sim/tools/convex/types.ts index a9a7d0b26c..a110315a47 100644 --- a/apps/sim/tools/convex/types.ts +++ b/apps/sim/tools/convex/types.ts @@ -60,6 +60,34 @@ export interface ConvexDocumentDeltasResponse extends ToolResponse { } } +/** + * 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 diff --git a/apps/sim/tools/convex/utils.ts b/apps/sim/tools/convex/utils.ts index 63596c107a..69712e6ded 100644 --- a/apps/sim/tools/convex/utils.ts +++ b/apps/sim/tools/convex/utils.ts @@ -1,4 +1,8 @@ -import type { ConvexFunctionCallResponse } from '@/tools/convex/types' +import { truncate } from '@sim/utils/string' +import type { + ConvexFunctionCallApiResponse, + ConvexFunctionCallResponse, +} from '@/tools/convex/types' /** * Builds a Convex deployment API URL from the user-provided deployment URL. @@ -47,6 +51,20 @@ export function parseFunctionArgs( 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 { + 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 @@ -57,7 +75,7 @@ export async function transformFunctionCallResponse( response: Response, functionType: 'query' | 'mutation' | 'action' | 'function' ): Promise { - const data = await response.json() + const data = (await parseConvexResponse(response)) as ConvexFunctionCallApiResponse if (data.status === 'error') { const details = From 31127eec7f2028464ce3c0b3d84ccc720eebe447 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 16:46:58 -0700 Subject: [PATCH 3/6] fix(convex): rename List Documents pagination cursor to pageCursor end-to-end for unambiguous chaining --- .../content/docs/en/integrations/convex.mdx | 6 +++--- apps/sim/blocks/blocks/convex.ts | 21 +++++++------------ apps/sim/lib/integrations/integrations.json | 2 +- apps/sim/tools/convex/list_documents.ts | 18 +++++++++------- apps/sim/tools/convex/types.ts | 5 +++-- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/convex.mdx b/apps/docs/content/docs/en/integrations/convex.mdx index db9b62f3e9..bc5bddff20 100644 --- a/apps/docs/content/docs/en/integrations/convex.mdx +++ b/apps/docs/content/docs/en/integrations/convex.mdx @@ -140,7 +140,7 @@ List all tables in a Convex deployment along with their JSON schemas ### `convex_list_documents` -List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and cursor back in to fetch the next page. +List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and page cursor back in to fetch the next page. #### Input @@ -150,7 +150,7 @@ List documents from a Convex table via a paginated snapshot. Pass the returned s | `deployKey` | string | Yes | Convex deploy key from the dashboard Settings page | | `tableName` | string | No | Table to list documents from. Omit to list documents from all tables. | | `snapshot` | string | No | Snapshot timestamp from a previous page. Omit on the first request to start a new snapshot. | -| `cursor` | string | No | Pagination cursor from a previous page. Omit on the first request. | +| `pageCursor` | string | No | Page cursor from a previous page of the same snapshot. Omit on the first request. | #### Output @@ -159,7 +159,7 @@ List documents from a Convex table via a paginated snapshot. Pass the returned s | `documents` | array | Documents in this page of the snapshot | | `hasMore` | boolean | Whether more pages remain in the snapshot | | `snapshot` | string | Snapshot timestamp to pass back in when fetching the next page | -| `cursor` | string | Pagination cursor to pass back in when fetching the next page | +| `pageCursor` | string | Page cursor to pass back in when fetching the next page | ### `convex_document_deltas` diff --git a/apps/sim/blocks/blocks/convex.ts b/apps/sim/blocks/blocks/convex.ts index f3a1e0bb38..8dd34e9c92 100644 --- a/apps/sim/blocks/blocks/convex.ts +++ b/apps/sim/blocks/blocks/convex.ts @@ -144,15 +144,6 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, throw new Error(`Invalid Convex operation: ${params.operation}`) } }, - params: (params) => { - const { pageCursor, ...rest } = params - - if (params.operation === 'list_documents') { - rest.cursor = pageCursor - } - - return rest - }, }, }, inputs: { @@ -164,7 +155,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, tableName: { type: 'string', description: 'Table to read from (empty for all tables)' }, snapshot: { type: 'string', description: 'Snapshot timestamp for List Documents pagination' }, cursor: { type: 'string', description: 'Timestamp cursor for Document Deltas' }, - pageCursor: { type: 'string', description: 'Pagination cursor for List Documents' }, + pageCursor: { type: 'string', description: 'Page cursor for List Documents pagination' }, }, outputs: { value: { @@ -193,11 +184,15 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, }, snapshot: { type: 'string', - description: 'Snapshot timestamp to pass back in for the next page', + description: 'Snapshot timestamp to pass back in for the next List Documents page', + }, + pageCursor: { + type: 'string', + description: 'Page cursor to pass back in for the next List Documents page', }, cursor: { type: 'string', - description: 'Cursor to pass back in for the next page', + description: 'Timestamp cursor to pass back in for the next Document Deltas page', }, }, } @@ -289,7 +284,7 @@ export const ConvexBlockMeta = { description: 'Page through a full Convex table snapshot with List Documents until hasMore is false.', content: - '# Export a Convex Table\n\nRead every document in a table using snapshot pagination so the export is consistent.\n\n## Steps\n1. Use the List Documents operation with the deployment URL, deploy key, and the table name (leave empty to export all tables).\n2. On the first call leave Snapshot and Cursor empty; the response pins a snapshot timestamp.\n3. While hasMore is true, call List Documents again passing back the returned snapshot and cursor values.\n4. Collect the documents arrays from each page into your destination.\n\n## Output\nA complete, point-in-time set of documents for the table, each including _id and _creationTime.', + '# Export a Convex Table\n\nRead every document in a table using snapshot pagination so the export is consistent.\n\n## Steps\n1. Use the List Documents operation with the deployment URL, deploy key, and the table name (leave empty to export all tables).\n2. On the first call leave Snapshot and Cursor empty; the response pins a snapshot timestamp.\n3. While hasMore is true, call List Documents again passing back the returned snapshot and pageCursor values.\n4. Collect the documents arrays from each page into your destination.\n\n## Output\nA complete, point-in-time set of documents for the table, each including _id and _creationTime.', }, { name: 'sync-convex-changes', diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 4cb3d61823..0a3f466833 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -3228,7 +3228,7 @@ }, { "name": "List Documents", - "description": "List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and cursor back in to fetch the next page." + "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." }, { "name": "Document Deltas", diff --git a/apps/sim/tools/convex/list_documents.ts b/apps/sim/tools/convex/list_documents.ts index 523b415d64..f14956b5ff 100644 --- a/apps/sim/tools/convex/list_documents.ts +++ b/apps/sim/tools/convex/list_documents.ts @@ -11,7 +11,7 @@ export const listDocumentsTool: ToolConfig Date: Thu, 11 Jun 2026 17:18:16 -0700 Subject: [PATCH 4/6] fix(convex): validate deployment URL with shared SSRF guard --- apps/sim/tools/convex/utils.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/convex/utils.ts b/apps/sim/tools/convex/utils.ts index 69712e6ded..ebc1999ebc 100644 --- a/apps/sim/tools/convex/utils.ts +++ b/apps/sim/tools/convex/utils.ts @@ -1,4 +1,5 @@ import { truncate } from '@sim/utils/string' +import { validateExternalUrl } from '@/lib/core/security/input-validation' import type { ConvexFunctionCallApiResponse, ConvexFunctionCallResponse, @@ -7,13 +8,16 @@ import type { /** * 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(/\/+$/, '') - if (!/^https?:\/\//.test(trimmed)) { - throw new Error( - 'Deployment URL must start with https:// (e.g., https://your-deployment.convex.cloud) or http:// for self-hosted deployments' - ) + const validation = validateExternalUrl(trimmed, 'Deployment URL') + if (!validation.isValid) { + throw new Error(`${validation.error} (e.g., https://your-deployment.convex.cloud)`) } return `${trimmed}${path}` } From ad189ac301712f315a9906e28be0ee0d7d977638 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 17:33:01 -0700 Subject: [PATCH 5/6] =?UTF-8?q?improvement(convex):=20polish=20from=20fina?= =?UTF-8?q?l=20validation=20pass=20=E2=80=94=20reject=20query=20strings=20?= =?UTF-8?q?in=20deployment=20URL,=20validate=20object=20args,=20fix=20sync?= =?UTF-8?q?=20skill=20wording?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/sim/blocks/blocks/convex.ts | 2 +- apps/sim/tools/convex/utils.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/convex.ts b/apps/sim/blocks/blocks/convex.ts index 8dd34e9c92..63ed6eb1cd 100644 --- a/apps/sim/blocks/blocks/convex.ts +++ b/apps/sim/blocks/blocks/convex.ts @@ -290,7 +290,7 @@ export const ConvexBlockMeta = { name: 'sync-convex-changes', description: 'Fetch only changed Convex documents since a snapshot using Document Deltas.', content: - '# Sync Convex Changes Incrementally\n\nAfter an initial export, keep a downstream copy fresh by reading only what changed.\n\n## Steps\n1. Run an initial export with List Documents and keep the final snapshot value.\n2. On each sync run, call Document Deltas with that value as the Cursor (and optionally a table name).\n3. While hasMore is true, keep calling Document Deltas with the returned cursor; persist the last cursor for the next run.\n4. Apply each document by _id; documents with _deleted set to true should be removed downstream.\n\n## Output\nThe changed documents since the stored cursor plus a new cursor to persist, giving exactly-once incremental sync.', + '# Sync Convex Changes Incrementally\n\nAfter an initial export, keep a downstream copy fresh by reading only what changed.\n\n## Steps\n1. Run an initial export with List Documents and keep the final snapshot value.\n2. On each sync run, call Document Deltas with that value as the Cursor (and optionally a table name).\n3. While hasMore is true, keep calling Document Deltas with the returned cursor; persist the last cursor for the next run.\n4. Apply each document by _id; documents with _deleted set to true should be removed downstream.\n\n## Output\nThe changed documents since the stored cursor plus a new cursor to persist, giving reliable incremental sync when documents are applied idempotently by _id.', }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/convex/utils.ts b/apps/sim/tools/convex/utils.ts index ebc1999ebc..324b0f5f83 100644 --- a/apps/sim/tools/convex/utils.ts +++ b/apps/sim/tools/convex/utils.ts @@ -19,6 +19,12 @@ export function convexApiUrl(deploymentUrl: string, path: string): string { 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}` } @@ -52,6 +58,9 @@ export function parseFunctionArgs( } return parsed as Record } + if (typeof args !== 'object' || Array.isArray(args)) { + throw new Error('Invalid function arguments: expected a JSON object, not an array or scalar') + } return args } From a0ba06e951e3f804158c76ed479b99e9dc94cd36 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 17:43:53 -0700 Subject: [PATCH 6/6] docs(convex): note streaming export plan requirement on data-export tools (verified via live E2E) --- apps/docs/content/docs/en/integrations/convex.mdx | 8 +++++--- apps/sim/lib/integrations/integrations.json | 6 +++--- apps/sim/tools/convex/document_deltas.ts | 2 +- apps/sim/tools/convex/list_documents.ts | 2 +- apps/sim/tools/convex/list_tables.ts | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/convex.mdx b/apps/docs/content/docs/en/integrations/convex.mdx index bc5bddff20..91e7ad4bbf 100644 --- a/apps/docs/content/docs/en/integrations/convex.mdx +++ b/apps/docs/content/docs/en/integrations/convex.mdx @@ -28,6 +28,8 @@ Sim's Convex integration connects your workflows to any Convex deployment with t - **Inspect your data model:** List Tables returns every table in the deployment with the JSON schema of its documents. - **Export and sync data:** List Documents pages through a consistent snapshot of a table, and Document Deltas returns only the documents that changed since a snapshot — including deletions — making incremental syncs to warehouses, search indexes, or other tools straightforward. +The Run Query, Run Mutation, Run Action, and Run Function operations work on every Convex plan. List Tables, List Documents, and Document Deltas use Convex's streaming export API, which is available on Convex paid plans. + Typical patterns include agents that read and write application data through your existing Convex functions, scheduled exports to analytics destinations, and change-driven automations that react to new or updated documents. {/* MANUAL-CONTENT-END */} @@ -122,7 +124,7 @@ Run any Convex function (query, mutation, or action) by path without specifying ### `convex_list_tables` -List all tables in a Convex deployment along with their JSON schemas +List all tables in a Convex deployment along with their JSON schemas. Requires streaming export, available on Convex paid plans. #### Input @@ -140,7 +142,7 @@ List all tables in a Convex deployment along with their JSON schemas ### `convex_list_documents` -List documents from a Convex table via a paginated snapshot. Pass the returned snapshot and page cursor back in to fetch the next page. +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. #### Input @@ -163,7 +165,7 @@ List documents from a Convex table via a paginated snapshot. Pass the returned s ### `convex_document_deltas` -List documents that changed after a snapshot or previous delta cursor. Deleted documents are returned with a _deleted flag. +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. #### Input diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 0a3f466833..ecee38c652 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -3224,15 +3224,15 @@ }, { "name": "List Tables", - "description": "List all tables in a Convex deployment along with their JSON schemas" + "description": "List all tables in a Convex deployment along with their JSON schemas. Requires streaming export, available on Convex paid plans." }, { "name": "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." + "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." }, { "name": "Document Deltas", - "description": "List documents that changed after a snapshot or previous delta cursor. Deleted documents are returned with a _deleted flag." + "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." } ], "operationCount": 7, diff --git a/apps/sim/tools/convex/document_deltas.ts b/apps/sim/tools/convex/document_deltas.ts index 2be0ed72b2..a07fd5516b 100644 --- a/apps/sim/tools/convex/document_deltas.ts +++ b/apps/sim/tools/convex/document_deltas.ts @@ -13,7 +13,7 @@ export const documentDeltasTool: ToolConfig< 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.', + '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: { diff --git a/apps/sim/tools/convex/list_documents.ts b/apps/sim/tools/convex/list_documents.ts index f14956b5ff..6c4f2650e2 100644 --- a/apps/sim/tools/convex/list_documents.ts +++ b/apps/sim/tools/convex/list_documents.ts @@ -11,7 +11,7 @@ export const listDocumentsTool: ToolConfig = { id: 'convex_list_tables', name: 'Convex List Tables', - description: 'List all tables in a Convex deployment along with their JSON schemas', + 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: {