diff --git a/apps/docs/content/docs/en/tools/insforge.mdx b/apps/docs/content/docs/en/tools/insforge.mdx
new file mode 100644
index 0000000000..5e7f2a9bc1
--- /dev/null
+++ b/apps/docs/content/docs/en/tools/insforge.mdx
@@ -0,0 +1,349 @@
+---
+title: InsForge
+description: Use InsForge backend services
+---
+
+import { BlockInfoCard } from "@/components/ui/block-info-card"
+
+
+
+{/* MANUAL-CONTENT-START:intro */}
+[InsForge](https://insforge.app/) is a powerful backend-as-a-service (BaaS) platform that provides developers with a comprehensive suite of tools to build, scale, and manage modern applications. InsForge offers a fully managed PostgreSQL database with PostgREST API, robust authentication, file storage, serverless functions, and AI capabilities—all accessible through a unified and developer-friendly interface.
+
+**Why InsForge?**
+- **Instant APIs:** Every table in your database is instantly available via REST endpoints, making it easy to build data-driven applications without writing custom backend code.
+- **AI Integration:** Built-in support for chat completions, vision analysis, and image generation using OpenAI-compatible APIs.
+- **Storage:** Securely upload, download, and manage files with built-in storage that integrates seamlessly with your database.
+- **Serverless Functions:** Deploy and invoke serverless functions for custom backend logic.
+
+**Using InsForge in Sim**
+
+Sim's InsForge integration makes it effortless to connect your agentic workflows to your InsForge projects. With just a few configuration fields—your Base URL, Table name, and API Key—you can securely interact with your database, storage, functions, and AI services directly from your Sim blocks. The integration abstracts away the complexity of API calls, letting you focus on building logic and automations.
+
+**Key benefits of using InsForge in Sim:**
+- **No-code/low-code database operations:** Query, insert, update, and delete rows in your InsForge tables without writing SQL or backend code.
+- **Flexible querying:** Use PostgREST filter syntax to perform advanced queries, including filtering, ordering, and limiting results.
+- **AI-powered workflows:** Generate text with chat completions, analyze images with vision, and create images with AI—all within your workflow.
+- **Seamless integration:** Easily connect InsForge to other tools and services in your workflow, enabling powerful automations.
+- **Secure and scalable:** All operations use your InsForge API Key, ensuring secure access to your data with the scalability of a managed cloud platform.
+
+Whether you're building internal tools, automating business processes, or powering production applications, InsForge in Sim provides a fast, reliable, and developer-friendly way to manage your data, storage, functions, and AI—no infrastructure management required.
+{/* MANUAL-CONTENT-END */}
+
+
+## Usage Instructions
+
+Integrate InsForge into the workflow. Supports database operations (query, get_row, insert, update, delete, upsert), storage management (upload, download, list, delete), serverless function invocation, and AI capabilities (chat completion, vision, image generation).
+
+
+
+## Tools
+
+### `insforge_query`
+
+Query data from an InsForge table
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to query |
+| `filter` | string | No | PostgREST filter \(e.g., "id=eq.123"\) |
+| `orderBy` | string | No | Column to order by \(add .desc for descending\) |
+| `limit` | number | No | Maximum number of rows to return |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array of records returned from the query |
+
+### `insforge_get_row`
+
+Get a single row from an InsForge table based on filter criteria
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to query |
+| `filter` | string | Yes | PostgREST filter to find the specific row \(e.g., "id=eq.123"\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array containing the row data if found, empty array if not found |
+
+### `insforge_insert`
+
+Insert data into an InsForge table
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to insert data into |
+| `data` | array | Yes | The data to insert \(array of objects or a single object\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array of inserted records |
+
+### `insforge_update`
+
+Update rows in an InsForge table based on filter criteria
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to update |
+| `filter` | string | Yes | PostgREST filter to identify rows to update \(e.g., "id=eq.123"\) |
+| `data` | object | Yes | Data to update in the matching rows |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array of updated records |
+
+### `insforge_delete`
+
+Delete rows from an InsForge table based on filter criteria
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to delete from |
+| `filter` | string | Yes | PostgREST filter to identify rows to delete \(e.g., "id=eq.123"\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array of deleted records |
+
+### `insforge_upsert`
+
+Insert or update data in an InsForge table (upsert operation)
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `table` | string | Yes | The name of the InsForge table to upsert data into |
+| `data` | array | Yes | The data to upsert \(insert or update\) - array of objects or a single object |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | array | Array of upserted records |
+
+### `insforge_storage_upload`
+
+Upload a file to an InsForge storage bucket
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `bucket` | string | Yes | The name of the storage bucket |
+| `path` | string | Yes | The path where the file will be stored \(e.g., "folder/file.jpg"\) |
+| `fileContent` | string | Yes | The file content \(base64 encoded for binary files, or plain text\) |
+| `contentType` | string | No | MIME type of the file \(e.g., "image/jpeg", "text/plain"\) |
+| `upsert` | boolean | No | If true, overwrites existing file \(default: false\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `results` | object | Upload result including file path and metadata |
+
+### `insforge_storage_download`
+
+Download a file from an InsForge storage bucket
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `bucket` | string | Yes | The name of the storage bucket |
+| `path` | string | Yes | The path to the file to download \(e.g., "folder/file.jpg"\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `content` | string | File content \(base64 encoded for binary files\) |
+| `contentType` | string | MIME type of the file |
+
+### `insforge_storage_list`
+
+List files in an InsForge storage bucket
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `bucket` | string | Yes | The name of the storage bucket |
+| `path` | string | No | The folder path to list files from \(default: root\) |
+| `limit` | number | No | Maximum number of files to return |
+| `offset` | number | No | Number of files to skip \(for pagination\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `files` | array | Array of file objects with metadata |
+
+### `insforge_storage_delete`
+
+Delete a file from an InsForge storage bucket
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `bucket` | string | Yes | The name of the storage bucket |
+| `path` | string | Yes | The path to the file to delete \(e.g., "folder/file.jpg"\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+
+### `insforge_invoke`
+
+Invoke an InsForge serverless function
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `functionName` | string | Yes | The name of the function to invoke |
+| `body` | object | No | Request body to pass to the function |
+| `method` | string | No | HTTP method \(GET, POST, PUT, DELETE\) - default: POST |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `result` | json | Result returned from the function |
+
+### `insforge_completion`
+
+Generate text using InsForge AI chat completion
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `model` | string | No | The model to use \(e.g., "gpt-4o-mini"\) |
+| `systemPrompt` | string | No | System message to set the assistant's behavior |
+| `prompt` | string | Yes | The user message/prompt to send |
+| `temperature` | number | No | Sampling temperature \(0-2\) |
+| `maxTokens` | number | No | Maximum tokens to generate |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `content` | string | The generated text response |
+| `usage` | object | Token usage statistics |
+
+### `insforge_vision`
+
+Analyze images using InsForge AI vision
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `model` | string | No | The vision model to use \(e.g., "gpt-4o"\) |
+| `imageUrl` | string | Yes | URL of the image to analyze |
+| `prompt` | string | Yes | Question or instruction about the image |
+| `maxTokens` | number | No | Maximum tokens to generate |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `content` | string | The vision analysis response |
+| `usage` | object | Token usage statistics |
+
+### `insforge_image_generation`
+
+Generate images using InsForge AI
+
+#### Input
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `baseUrl` | string | Yes | Your InsForge backend URL \(e.g., https://your-app.insforge.app\) |
+| `model` | string | No | The image generation model to use \(e.g., "dall-e-3"\) |
+| `prompt` | string | Yes | The prompt describing the image to generate |
+| `size` | string | No | Image size \(e.g., "1024x1024", "1792x1024", "1024x1792"\) |
+| `quality` | string | No | Image quality \("standard" or "hd"\) |
+| `n` | number | No | Number of images to generate \(default: 1\) |
+| `apiKey` | string | Yes | Your InsForge anon key or service role key |
+
+#### Output
+
+| Parameter | Type | Description |
+| --------- | ---- | ----------- |
+| `message` | string | Operation status message |
+| `images` | array | Array of generated images with URLs |
+
+
+
+## Notes
+
+- Category: `tools`
+- Type: `insforge`
diff --git a/apps/sim/blocks/blocks/insforge.ts b/apps/sim/blocks/blocks/insforge.ts
new file mode 100644
index 0000000000..11e2d61ea7
--- /dev/null
+++ b/apps/sim/blocks/blocks/insforge.ts
@@ -0,0 +1,543 @@
+import { InsForgeIcon } from '@/components/icons'
+import { AuthMode, type BlockConfig } from '@/blocks/types'
+import type { InsForgeBaseResponse } from '@/tools/insforge/types'
+
+export const InsForgeBlock: BlockConfig = {
+ type: 'insforge',
+ name: 'InsForge',
+ description: 'Use InsForge backend',
+ authMode: AuthMode.ApiKey,
+ longDescription:
+ 'Integrate InsForge into the workflow. Supports database operations (query, insert, update, delete, upsert), storage management (upload, download, list, delete files), serverless function invocation, and AI capabilities (chat completions, vision, image generation).',
+ docsLink: 'https://docs.sim.ai/tools/insforge',
+ category: 'tools',
+ bgColor: '#000000',
+ icon: InsForgeIcon,
+ subBlocks: [
+ {
+ id: 'operation',
+ title: 'Operation',
+ type: 'dropdown',
+ options: [
+ // Database Operations
+ { label: 'Get Many Rows', id: 'query' },
+ { label: 'Get a Row', id: 'get_row' },
+ { label: 'Create a Row', id: 'insert' },
+ { label: 'Update a Row', id: 'update' },
+ { label: 'Delete a Row', id: 'delete' },
+ { label: 'Upsert a Row', id: 'upsert' },
+ // Storage Operations
+ { label: 'Storage: Upload File', id: 'storage_upload' },
+ { label: 'Storage: Download File', id: 'storage_download' },
+ { label: 'Storage: List Files', id: 'storage_list' },
+ { label: 'Storage: Delete Files', id: 'storage_delete' },
+ // Functions
+ { label: 'Invoke Function', id: 'invoke' },
+ // AI Operations
+ { label: 'AI: Chat Completion', id: 'completion' },
+ { label: 'AI: Vision', id: 'vision' },
+ { label: 'AI: Image Generation', id: 'image_generation' },
+ ],
+ value: () => 'query',
+ },
+ {
+ id: 'baseUrl',
+ title: 'Base URL',
+ type: 'short-input',
+ placeholder: 'https://your-app.insforge.app',
+ required: true,
+ },
+ {
+ id: 'apiKey',
+ title: 'API Key',
+ type: 'short-input',
+ placeholder: 'Your InsForge anon key or service role key',
+ password: true,
+ required: true,
+ },
+ // Database table field
+ {
+ id: 'table',
+ title: 'Table',
+ type: 'short-input',
+ placeholder: 'Name of the table',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: ['query', 'get_row', 'insert', 'update', 'delete', 'upsert'],
+ },
+ },
+ // Data input for create/update operations
+ {
+ id: 'data',
+ title: 'Data',
+ type: 'code',
+ placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
+ condition: { field: 'operation', value: 'insert' },
+ required: true,
+ },
+ {
+ id: 'data',
+ title: 'Data',
+ type: 'code',
+ placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
+ condition: { field: 'operation', value: 'update' },
+ required: true,
+ },
+ {
+ id: 'data',
+ title: 'Data',
+ type: 'code',
+ placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
+ condition: { field: 'operation', value: 'upsert' },
+ required: true,
+ },
+ // Filter for get_row, update, delete operations (required)
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'get_row' },
+ required: true,
+ },
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'update' },
+ required: true,
+ },
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'delete' },
+ required: true,
+ },
+ // Optional filter for query operation
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ placeholder: 'status=eq.active',
+ condition: { field: 'operation', value: 'query' },
+ },
+ // Optional order by for query operation
+ {
+ id: 'orderBy',
+ title: 'Order By',
+ type: 'short-input',
+ placeholder: 'column_name (add DESC for descending)',
+ condition: { field: 'operation', value: 'query' },
+ },
+ // Optional limit for query operation
+ {
+ id: 'limit',
+ title: 'Limit',
+ type: 'short-input',
+ placeholder: '100',
+ condition: { field: 'operation', value: 'query' },
+ },
+ // Storage bucket field
+ {
+ id: 'bucket',
+ title: 'Bucket Name',
+ type: 'short-input',
+ placeholder: 'my-bucket',
+ condition: {
+ field: 'operation',
+ value: ['storage_upload', 'storage_download', 'storage_list', 'storage_delete'],
+ },
+ required: true,
+ },
+ // Storage Upload fields
+ {
+ id: 'path',
+ title: 'File Path',
+ type: 'short-input',
+ placeholder: 'folder/file.jpg',
+ condition: { field: 'operation', value: 'storage_upload' },
+ required: true,
+ },
+ {
+ id: 'fileContent',
+ title: 'File Content',
+ type: 'code',
+ placeholder: 'Base64 encoded for binary files, or plain text',
+ condition: { field: 'operation', value: 'storage_upload' },
+ required: true,
+ },
+ {
+ id: 'contentType',
+ title: 'Content Type (MIME)',
+ type: 'short-input',
+ placeholder: 'image/jpeg',
+ condition: { field: 'operation', value: 'storage_upload' },
+ },
+ {
+ id: 'upsert',
+ title: 'Upsert (overwrite if exists)',
+ type: 'dropdown',
+ options: [
+ { label: 'False', id: 'false' },
+ { label: 'True', id: 'true' },
+ ],
+ value: () => 'false',
+ condition: { field: 'operation', value: 'storage_upload' },
+ },
+ // Storage Download fields
+ {
+ id: 'path',
+ title: 'File Path',
+ type: 'short-input',
+ placeholder: 'folder/file.jpg',
+ condition: { field: 'operation', value: 'storage_download' },
+ required: true,
+ },
+ {
+ id: 'fileName',
+ title: 'File Name Override',
+ type: 'short-input',
+ placeholder: 'my-file.jpg',
+ condition: { field: 'operation', value: 'storage_download' },
+ },
+ // Storage List fields
+ {
+ id: 'path',
+ title: 'Folder Path',
+ type: 'short-input',
+ placeholder: 'folder/',
+ condition: { field: 'operation', value: 'storage_list' },
+ },
+ {
+ id: 'limit',
+ title: 'Limit',
+ type: 'short-input',
+ placeholder: '100',
+ condition: { field: 'operation', value: 'storage_list' },
+ },
+ {
+ id: 'offset',
+ title: 'Offset',
+ type: 'short-input',
+ placeholder: '0',
+ condition: { field: 'operation', value: 'storage_list' },
+ },
+ // Storage Delete fields
+ {
+ id: 'path',
+ title: 'File Path',
+ type: 'short-input',
+ placeholder: 'folder/file.jpg',
+ condition: { field: 'operation', value: 'storage_delete' },
+ required: true,
+ },
+ // Functions fields
+ {
+ id: 'functionName',
+ title: 'Function Name',
+ type: 'short-input',
+ placeholder: 'my-function',
+ condition: { field: 'operation', value: 'invoke' },
+ required: true,
+ },
+ {
+ id: 'body',
+ title: 'Request Body (JSON)',
+ type: 'code',
+ placeholder: '{\n "key": "value"\n}',
+ condition: { field: 'operation', value: 'invoke' },
+ },
+ // AI Completion fields
+ {
+ id: 'model',
+ title: 'Model',
+ type: 'short-input',
+ placeholder: 'gpt-4o-mini',
+ condition: { field: 'operation', value: 'completion' },
+ },
+ {
+ id: 'messages',
+ title: 'Messages (JSON array)',
+ type: 'code',
+ placeholder:
+ '[\n {"role": "system", "content": "You are a helpful assistant."},\n {"role": "user", "content": "Hello!"}\n]',
+ condition: { field: 'operation', value: 'completion' },
+ required: true,
+ },
+ {
+ id: 'temperature',
+ title: 'Temperature',
+ type: 'short-input',
+ placeholder: '1.0',
+ condition: { field: 'operation', value: 'completion' },
+ },
+ {
+ id: 'maxTokens',
+ title: 'Max Tokens',
+ type: 'short-input',
+ placeholder: '1000',
+ condition: { field: 'operation', value: 'completion' },
+ },
+ // AI Vision fields
+ {
+ id: 'model',
+ title: 'Model',
+ type: 'short-input',
+ placeholder: 'gpt-4o',
+ condition: { field: 'operation', value: 'vision' },
+ },
+ {
+ id: 'prompt',
+ title: 'Prompt',
+ type: 'long-input',
+ placeholder: 'Describe what you see in this image...',
+ condition: { field: 'operation', value: 'vision' },
+ required: true,
+ },
+ {
+ id: 'imageUrl',
+ title: 'Image URL',
+ type: 'short-input',
+ placeholder: 'https://example.com/image.jpg',
+ condition: { field: 'operation', value: 'vision' },
+ required: true,
+ },
+ {
+ id: 'maxTokens',
+ title: 'Max Tokens',
+ type: 'short-input',
+ placeholder: '1000',
+ condition: { field: 'operation', value: 'vision' },
+ },
+ // AI Image Generation fields
+ {
+ id: 'model',
+ title: 'Model',
+ type: 'short-input',
+ placeholder: 'dall-e-3',
+ condition: { field: 'operation', value: 'image_generation' },
+ },
+ {
+ id: 'prompt',
+ title: 'Prompt',
+ type: 'long-input',
+ placeholder: 'A beautiful sunset over the ocean...',
+ condition: { field: 'operation', value: 'image_generation' },
+ required: true,
+ },
+ {
+ id: 'size',
+ title: 'Size',
+ type: 'dropdown',
+ options: [
+ { label: '1024x1024', id: '1024x1024' },
+ { label: '1792x1024', id: '1792x1024' },
+ { label: '1024x1792', id: '1024x1792' },
+ ],
+ value: () => '1024x1024',
+ condition: { field: 'operation', value: 'image_generation' },
+ },
+ {
+ id: 'quality',
+ title: 'Quality',
+ type: 'dropdown',
+ options: [
+ { label: 'Standard', id: 'standard' },
+ { label: 'HD', id: 'hd' },
+ ],
+ value: () => 'standard',
+ condition: { field: 'operation', value: 'image_generation' },
+ },
+ {
+ id: 'n',
+ title: 'Number of Images',
+ type: 'short-input',
+ placeholder: '1',
+ condition: { field: 'operation', value: 'image_generation' },
+ },
+ ],
+ tools: {
+ access: [
+ 'insforge_query',
+ 'insforge_get_row',
+ 'insforge_insert',
+ 'insforge_update',
+ 'insforge_delete',
+ 'insforge_upsert',
+ 'insforge_storage_upload',
+ 'insforge_storage_download',
+ 'insforge_storage_list',
+ 'insforge_storage_delete',
+ 'insforge_invoke',
+ 'insforge_completion',
+ 'insforge_vision',
+ 'insforge_image_generation',
+ ],
+ config: {
+ tool: (params) => {
+ switch (params.operation) {
+ case 'query':
+ return 'insforge_query'
+ case 'get_row':
+ return 'insforge_get_row'
+ case 'insert':
+ return 'insforge_insert'
+ case 'update':
+ return 'insforge_update'
+ case 'delete':
+ return 'insforge_delete'
+ case 'upsert':
+ return 'insforge_upsert'
+ case 'storage_upload':
+ return 'insforge_storage_upload'
+ case 'storage_download':
+ return 'insforge_storage_download'
+ case 'storage_list':
+ return 'insforge_storage_list'
+ case 'storage_delete':
+ return 'insforge_storage_delete'
+ case 'invoke':
+ return 'insforge_invoke'
+ case 'completion':
+ return 'insforge_completion'
+ case 'vision':
+ return 'insforge_vision'
+ case 'image_generation':
+ return 'insforge_image_generation'
+ default:
+ throw new Error(`Invalid InsForge operation: ${params.operation}`)
+ }
+ },
+ params: (params) => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { operation, data, body, messages, upsert, ...rest } = params
+
+ // Parse JSON data if it's a string
+ let parsedData
+ if (data && typeof data === 'string' && data.trim()) {
+ try {
+ parsedData = JSON.parse(data)
+ } catch (parseError) {
+ const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
+ throw new Error(`Invalid JSON data format: ${errorMsg}`)
+ }
+ } else if (data && typeof data === 'object') {
+ parsedData = data
+ }
+
+ // Handle body for function invoke
+ let parsedBody
+ if (body && typeof body === 'string' && body.trim()) {
+ try {
+ parsedBody = JSON.parse(body)
+ } catch (parseError) {
+ const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
+ throw new Error(`Invalid body format: ${errorMsg}`)
+ }
+ } else if (body && typeof body === 'object') {
+ parsedBody = body
+ }
+
+ // Handle messages for AI completion
+ let parsedMessages
+ if (messages && typeof messages === 'string' && messages.trim()) {
+ try {
+ parsedMessages = JSON.parse(messages)
+ } catch (parseError) {
+ const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error'
+ throw new Error(`Invalid messages format: ${errorMsg}`)
+ }
+ } else if (messages && Array.isArray(messages)) {
+ parsedMessages = messages
+ }
+
+ // Convert string booleans to actual booleans
+ const parsedUpsert = upsert === 'true' || upsert === true
+
+ // Build params object, only including defined values
+ const result = { ...rest }
+
+ if (parsedData !== undefined) {
+ result.data = parsedData
+ }
+
+ if (parsedBody !== undefined) {
+ result.body = parsedBody
+ }
+
+ if (parsedMessages !== undefined) {
+ result.messages = parsedMessages
+ }
+
+ if (upsert !== undefined) {
+ result.upsert = parsedUpsert
+ }
+
+ return result
+ },
+ },
+ },
+ inputs: {
+ operation: { type: 'string', description: 'Operation to perform' },
+ baseUrl: { type: 'string', description: 'InsForge backend URL' },
+ apiKey: { type: 'string', description: 'API key' },
+ // Database inputs
+ table: { type: 'string', description: 'Database table name' },
+ data: { type: 'json', description: 'Row data' },
+ filter: { type: 'string', description: 'PostgREST filter syntax' },
+ orderBy: { type: 'string', description: 'Sort column' },
+ limit: { type: 'number', description: 'Result limit' },
+ offset: { type: 'number', description: 'Number of rows to skip' },
+ // Storage inputs
+ bucket: { type: 'string', description: 'Storage bucket name' },
+ path: { type: 'string', description: 'File path in storage' },
+ fileContent: { type: 'string', description: 'File content (base64 for binary)' },
+ contentType: { type: 'string', description: 'MIME type of the file' },
+ fileName: { type: 'string', description: 'Optional filename override' },
+ upsert: { type: 'boolean', description: 'Whether to overwrite existing file' },
+ paths: { type: 'array', description: 'Array of file paths' },
+ // Functions inputs
+ functionName: { type: 'string', description: 'Name of the function to invoke' },
+ body: { type: 'json', description: 'Request body for function' },
+ // AI inputs
+ model: { type: 'string', description: 'AI model to use' },
+ messages: { type: 'array', description: 'Chat messages' },
+ temperature: { type: 'number', description: 'Sampling temperature' },
+ maxTokens: { type: 'number', description: 'Maximum tokens to generate' },
+ prompt: { type: 'string', description: 'Prompt for AI operations' },
+ imageUrl: { type: 'string', description: 'URL of image to analyze' },
+ size: { type: 'string', description: 'Image size for generation' },
+ quality: { type: 'string', description: 'Image quality for generation' },
+ n: { type: 'number', description: 'Number of images to generate' },
+ },
+ outputs: {
+ message: {
+ type: 'string',
+ description: 'Success or error message describing the operation outcome',
+ },
+ results: {
+ type: 'json',
+ description: 'Database records, storage objects, or operation results',
+ },
+ file: {
+ type: 'files',
+ description: 'Downloaded file stored in execution files',
+ },
+ content: {
+ type: 'string',
+ description: 'AI generated text content',
+ },
+ images: {
+ type: 'array',
+ description: 'Generated images with URLs',
+ },
+ usage: {
+ type: 'json',
+ description: 'Token usage statistics for AI operations',
+ },
+ },
+}
diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts
index bd5b96f6bd..9c52910c76 100644
--- a/apps/sim/blocks/registry.ts
+++ b/apps/sim/blocks/registry.ts
@@ -49,6 +49,7 @@ import { HunterBlock } from '@/blocks/blocks/hunter'
import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator'
import { IncidentioBlock } from '@/blocks/blocks/incidentio'
import { InputTriggerBlock } from '@/blocks/blocks/input_trigger'
+import { InsForgeBlock } from '@/blocks/blocks/insforge'
import { IntercomBlock } from '@/blocks/blocks/intercom'
import { JinaBlock } from '@/blocks/blocks/jina'
import { JiraBlock } from '@/blocks/blocks/jira'
@@ -190,6 +191,7 @@ export const registry: Record = {
hunter: HunterBlock,
image_generator: ImageGeneratorBlock,
incidentio: IncidentioBlock,
+ insforge: InsForgeBlock,
input_trigger: InputTriggerBlock,
intercom: IntercomBlock,
jina: JinaBlock,
diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx
index ddaf0f95ee..dbfbb24b02 100644
--- a/apps/sim/components/icons.tsx
+++ b/apps/sim/components/icons.tsx
@@ -4288,3 +4288,22 @@ export function SpotifyIcon(props: SVGProps) {
)
}
+
+export function InsForgeIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/apps/sim/tools/insforge/completion.ts b/apps/sim/tools/insforge/completion.ts
new file mode 100644
index 0000000000..a2e3e6cbec
--- /dev/null
+++ b/apps/sim/tools/insforge/completion.ts
@@ -0,0 +1,114 @@
+import type { InsForgeCompletionParams, InsForgeCompletionResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const completionTool: ToolConfig = {
+ id: 'insforge_completion',
+ name: 'InsForge AI Completion',
+ description: 'Generate AI chat completions using InsForge',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ model: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'The model to use (e.g., "gpt-4o", "gpt-4o-mini")',
+ },
+ messages: {
+ type: 'array',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'Array of messages with role (system/user/assistant) and content',
+ },
+ temperature: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Sampling temperature (0-2, default: 1)',
+ },
+ maxTokens: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Maximum tokens to generate',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/ai/chat/completion`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {
+ messages: params.messages,
+ }
+
+ if (params.model) {
+ body.model = params.model
+ }
+
+ if (params.temperature !== undefined) {
+ body.temperature = params.temperature
+ }
+
+ if (params.maxTokens) {
+ body.max_tokens = params.maxTokens
+ }
+
+ return body
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge AI completion response: ${parseError}`)
+ }
+
+ const content = data?.choices?.[0]?.message?.content || ''
+ const usage = data?.usage
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully generated completion',
+ content,
+ usage: usage
+ ? {
+ promptTokens: usage.prompt_tokens,
+ completionTokens: usage.completion_tokens,
+ totalTokens: usage.total_tokens,
+ }
+ : undefined,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ content: { type: 'string', description: 'Generated completion text' },
+ usage: { type: 'json', description: 'Token usage statistics' },
+ },
+}
diff --git a/apps/sim/tools/insforge/delete.ts b/apps/sim/tools/insforge/delete.ts
new file mode 100644
index 0000000000..c9b1404f30
--- /dev/null
+++ b/apps/sim/tools/insforge/delete.ts
@@ -0,0 +1,101 @@
+import type { InsForgeDeleteParams, InsForgeDeleteResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const deleteTool: ToolConfig = {
+ id: 'insforge_delete',
+ name: 'InsForge Delete',
+ description: 'Delete rows from an InsForge database table based on filter criteria',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to delete from',
+ },
+ filter: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'PostgREST filter to identify rows to delete (e.g., "id=eq.123")',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+
+ if (params.filter?.trim()) {
+ url += `&${params.filter.trim()}`
+ } else {
+ throw new Error(
+ 'Filter is required for delete operations to prevent accidental deletion of all rows'
+ )
+ }
+
+ return url
+ },
+ method: 'DELETE',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ Prefer: 'return=representation',
+ }),
+ },
+
+ transformResponse: async (response: Response) => {
+ const text = await response.text()
+ let data
+
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+ } else {
+ data = []
+ }
+
+ const deletedCount = Array.isArray(data) ? data.length : 0
+
+ if (deletedCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No rows were deleted (no matching records found)',
+ results: data,
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully deleted ${deletedCount} row${deletedCount === 1 ? '' : 's'} from InsForge`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of deleted records' },
+ },
+}
diff --git a/apps/sim/tools/insforge/get_row.ts b/apps/sim/tools/insforge/get_row.ts
new file mode 100644
index 0000000000..88751592ee
--- /dev/null
+++ b/apps/sim/tools/insforge/get_row.ts
@@ -0,0 +1,94 @@
+import type { InsForgeGetRowParams, InsForgeGetRowResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const getRowTool: ToolConfig = {
+ id: 'insforge_get_row',
+ name: 'InsForge Get Row',
+ description: 'Get a single row from an InsForge database table based on filter criteria',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to query',
+ },
+ filter: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'PostgREST filter to find the specific row (e.g., "id=eq.123")',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+
+ if (params.filter?.trim()) {
+ url += `&${params.filter.trim()}`
+ }
+
+ url += '&limit=1'
+ return url
+ },
+ method: 'GET',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+
+ const rowCount = Array.isArray(data) ? data.length : 0
+
+ if (rowCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No row found matching the filter criteria',
+ results: [],
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully retrieved row from InsForge',
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: {
+ type: 'array',
+ description: 'Array containing the row data if found, empty array if not found',
+ },
+ },
+}
diff --git a/apps/sim/tools/insforge/image_generation.ts b/apps/sim/tools/insforge/image_generation.ts
new file mode 100644
index 0000000000..0bed892ad7
--- /dev/null
+++ b/apps/sim/tools/insforge/image_generation.ts
@@ -0,0 +1,125 @@
+import type {
+ InsForgeImageGenerationParams,
+ InsForgeImageGenerationResponse,
+} from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const imageGenerationTool: ToolConfig<
+ InsForgeImageGenerationParams,
+ InsForgeImageGenerationResponse
+> = {
+ id: 'insforge_image_generation',
+ name: 'InsForge AI Image Generation',
+ description: 'Generate images using InsForge AI',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ model: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'The image generation model to use (e.g., "dall-e-3")',
+ },
+ prompt: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The prompt describing the image to generate',
+ },
+ size: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Image size (e.g., "1024x1024", "1792x1024", "1024x1792")',
+ },
+ quality: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Image quality ("standard" or "hd")',
+ },
+ n: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Number of images to generate (default: 1)',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/ai/image/generation`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {
+ prompt: params.prompt,
+ }
+
+ if (params.model) {
+ body.model = params.model
+ }
+
+ if (params.size) {
+ body.size = params.size
+ }
+
+ if (params.quality) {
+ body.quality = params.quality
+ }
+
+ if (params.n) {
+ body.n = params.n
+ }
+
+ return body
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge AI image generation response: ${parseError}`)
+ }
+
+ const images =
+ data?.data?.map((img: { url: string; revised_prompt?: string }) => ({
+ url: img.url,
+ revisedPrompt: img.revised_prompt,
+ })) || []
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully generated ${images.length} image${images.length === 1 ? '' : 's'}`,
+ images,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ images: { type: 'array', description: 'Array of generated images with URLs' },
+ },
+}
diff --git a/apps/sim/tools/insforge/index.ts b/apps/sim/tools/insforge/index.ts
new file mode 100644
index 0000000000..14f74ea260
--- /dev/null
+++ b/apps/sim/tools/insforge/index.ts
@@ -0,0 +1,29 @@
+import { completionTool } from '@/tools/insforge/completion'
+import { deleteTool } from '@/tools/insforge/delete'
+import { getRowTool } from '@/tools/insforge/get_row'
+import { imageGenerationTool } from '@/tools/insforge/image_generation'
+import { insertTool } from '@/tools/insforge/insert'
+import { invokeTool } from '@/tools/insforge/invoke'
+import { queryTool } from '@/tools/insforge/query'
+import { storageDeleteTool } from '@/tools/insforge/storage_delete'
+import { storageDownloadTool } from '@/tools/insforge/storage_download'
+import { storageListTool } from '@/tools/insforge/storage_list'
+import { storageUploadTool } from '@/tools/insforge/storage_upload'
+import { updateTool } from '@/tools/insforge/update'
+import { upsertTool } from '@/tools/insforge/upsert'
+import { visionTool } from '@/tools/insforge/vision'
+
+export const insforgeQueryTool = queryTool
+export const insforgeGetRowTool = getRowTool
+export const insforgeInsertTool = insertTool
+export const insforgeUpdateTool = updateTool
+export const insforgeDeleteTool = deleteTool
+export const insforgeUpsertTool = upsertTool
+export const insforgeStorageUploadTool = storageUploadTool
+export const insforgeStorageDownloadTool = storageDownloadTool
+export const insforgeStorageListTool = storageListTool
+export const insforgeStorageDeleteTool = storageDeleteTool
+export const insforgeInvokeTool = invokeTool
+export const insforgeCompletionTool = completionTool
+export const insforgeVisionTool = visionTool
+export const insforgeImageGenerationTool = imageGenerationTool
diff --git a/apps/sim/tools/insforge/insert.ts b/apps/sim/tools/insforge/insert.ts
new file mode 100644
index 0000000000..40f87173a4
--- /dev/null
+++ b/apps/sim/tools/insforge/insert.ts
@@ -0,0 +1,97 @@
+import type { InsForgeInsertParams, InsForgeInsertResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const insertTool: ToolConfig = {
+ id: 'insforge_insert',
+ name: 'InsForge Insert',
+ description: 'Insert data into an InsForge database table',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to insert data into',
+ },
+ data: {
+ type: 'json',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The data to insert (array of objects or a single object)',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ Prefer: 'return=representation',
+ }),
+ body: (params) => {
+ const dataToSend =
+ typeof params.data === 'object' && !Array.isArray(params.data) ? [params.data] : params.data
+ return dataToSend
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ const text = await response.text()
+ let data
+
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+ } else {
+ data = []
+ }
+
+ const insertedCount = Array.isArray(data) ? data.length : 0
+
+ if (insertedCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No rows were inserted',
+ results: data,
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully inserted ${insertedCount} row${insertedCount === 1 ? '' : 's'} into InsForge`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of inserted records' },
+ },
+}
diff --git a/apps/sim/tools/insforge/invoke.ts b/apps/sim/tools/insforge/invoke.ts
new file mode 100644
index 0000000000..1c4f17c891
--- /dev/null
+++ b/apps/sim/tools/insforge/invoke.ts
@@ -0,0 +1,88 @@
+import type { InsForgeInvokeParams, InsForgeInvokeResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const invokeTool: ToolConfig = {
+ id: 'insforge_invoke',
+ name: 'InsForge Invoke Function',
+ description: 'Invoke a serverless function in InsForge',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ functionName: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the function to invoke',
+ },
+ method: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'HTTP method (GET, POST, PUT, DELETE). Default: POST',
+ },
+ body: {
+ type: 'json',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'The request body to send to the function (JSON object)',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/functions/${encodeURIComponent(params.functionName)}`
+ },
+ method: (params) => (params.method as 'GET' | 'POST' | 'PUT' | 'DELETE') || 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => params.body || {},
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ const text = await response.text()
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch {
+ data = { result: text }
+ }
+ } else {
+ data = {}
+ }
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge function response: ${parseError}`)
+ }
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully invoked function',
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'json', description: 'Result returned from the function' },
+ },
+}
diff --git a/apps/sim/tools/insforge/query.ts b/apps/sim/tools/insforge/query.ts
new file mode 100644
index 0000000000..e7435ce645
--- /dev/null
+++ b/apps/sim/tools/insforge/query.ts
@@ -0,0 +1,118 @@
+import type { InsForgeQueryParams, InsForgeQueryResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const queryTool: ToolConfig = {
+ id: 'insforge_query',
+ name: 'InsForge Query',
+ description: 'Query data from an InsForge database table',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to query',
+ },
+ filter: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'PostgREST filter (e.g., "id=eq.123")',
+ },
+ orderBy: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Column to order by (add DESC for descending)',
+ },
+ limit: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Maximum number of rows to return',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+
+ if (params.filter?.trim()) {
+ url += `&${params.filter.trim()}`
+ }
+
+ if (params.orderBy) {
+ let orderParam = params.orderBy.trim()
+ if (/\s+DESC$/i.test(orderParam)) {
+ orderParam = `${orderParam.replace(/\s+DESC$/i, '').trim()}.desc`
+ } else if (/\s+ASC$/i.test(orderParam)) {
+ orderParam = `${orderParam.replace(/\s+ASC$/i, '').trim()}.asc`
+ } else {
+ orderParam = `${orderParam}.asc`
+ }
+ url += `&order=${orderParam}`
+ }
+
+ if (params.limit) {
+ url += `&limit=${Number(params.limit)}`
+ }
+
+ return url
+ },
+ method: 'GET',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+
+ const rowCount = Array.isArray(data) ? data.length : 0
+
+ if (rowCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No rows found matching the query criteria',
+ results: data,
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully queried ${rowCount} row${rowCount === 1 ? '' : 's'} from InsForge`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of records returned from the query' },
+ },
+}
diff --git a/apps/sim/tools/insforge/storage_delete.ts b/apps/sim/tools/insforge/storage_delete.ts
new file mode 100644
index 0000000000..e97c424be9
--- /dev/null
+++ b/apps/sim/tools/insforge/storage_delete.ts
@@ -0,0 +1,86 @@
+import type {
+ InsForgeStorageDeleteParams,
+ InsForgeStorageDeleteResponse,
+} from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const storageDeleteTool: ToolConfig<
+ InsForgeStorageDeleteParams,
+ InsForgeStorageDeleteResponse
+> = {
+ id: 'insforge_storage_delete',
+ name: 'InsForge Storage Delete',
+ description: 'Delete a file from an InsForge storage bucket',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ bucket: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the storage bucket',
+ },
+ path: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The file path to delete (e.g., "folder/file.jpg")',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}`
+ },
+ method: 'DELETE',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ const text = await response.text()
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch {
+ data = { result: text }
+ }
+ } else {
+ data = {}
+ }
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge storage delete response: ${parseError}`)
+ }
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully deleted file from storage',
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'json', description: 'Delete operation result' },
+ },
+}
diff --git a/apps/sim/tools/insforge/storage_download.ts b/apps/sim/tools/insforge/storage_download.ts
new file mode 100644
index 0000000000..888330a3ab
--- /dev/null
+++ b/apps/sim/tools/insforge/storage_download.ts
@@ -0,0 +1,124 @@
+import { createLogger } from '@/lib/logs/console/logger'
+import type {
+ InsForgeStorageDownloadParams,
+ InsForgeStorageDownloadResponse,
+} from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+const logger = createLogger('InsForgeStorageDownloadTool')
+
+export const storageDownloadTool: ToolConfig<
+ InsForgeStorageDownloadParams,
+ InsForgeStorageDownloadResponse
+> = {
+ id: 'insforge_storage_download',
+ name: 'InsForge Storage Download',
+ description: 'Download a file from an InsForge storage bucket',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ bucket: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the storage bucket',
+ },
+ path: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The path to the file to download (e.g., "folder/file.jpg")',
+ },
+ fileName: {
+ type: 'string',
+ required: false,
+ visibility: 'user-only',
+ description: 'Optional filename override',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}`
+ },
+ method: 'GET',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ },
+
+ transformResponse: async (response: Response, params?: InsForgeStorageDownloadParams) => {
+ try {
+ if (!response.ok) {
+ logger.error('Failed to download file from InsForge storage', {
+ status: response.status,
+ statusText: response.statusText,
+ })
+ throw new Error(`Failed to download file: ${response.statusText}`)
+ }
+
+ const contentType = response.headers.get('content-type') || 'application/octet-stream'
+
+ const pathParts = params?.path?.split('/') || []
+ const defaultFileName = pathParts[pathParts.length - 1] || 'download'
+ const resolvedName = params?.fileName || defaultFileName
+
+ logger.info('Downloading file from InsForge storage', {
+ bucket: params?.bucket,
+ path: params?.path,
+ fileName: resolvedName,
+ contentType,
+ })
+
+ const arrayBuffer = await response.arrayBuffer()
+ const fileBuffer = Buffer.from(arrayBuffer)
+
+ logger.info('File downloaded successfully from InsForge storage', {
+ name: resolvedName,
+ size: fileBuffer.length,
+ contentType,
+ })
+
+ const base64Data = fileBuffer.toString('base64')
+
+ return {
+ success: true,
+ output: {
+ file: {
+ name: resolvedName,
+ mimeType: contentType,
+ data: base64Data,
+ size: fileBuffer.length,
+ },
+ },
+ error: undefined,
+ }
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
+ const errorStack = error instanceof Error ? error.stack : undefined
+ logger.error('Error downloading file from InsForge storage', {
+ error: errorMessage,
+ stack: errorStack,
+ })
+ throw error
+ }
+ },
+
+ outputs: {
+ file: { type: 'file', description: 'Downloaded file stored in execution files' },
+ },
+}
diff --git a/apps/sim/tools/insforge/storage_list.ts b/apps/sim/tools/insforge/storage_list.ts
new file mode 100644
index 0000000000..fc01059fb8
--- /dev/null
+++ b/apps/sim/tools/insforge/storage_list.ts
@@ -0,0 +1,104 @@
+import type { InsForgeStorageListParams, InsForgeStorageListResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const storageListTool: ToolConfig = {
+ id: 'insforge_storage_list',
+ name: 'InsForge Storage List',
+ description: 'List files in an InsForge storage bucket',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ bucket: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the storage bucket',
+ },
+ path: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'The folder path to list files from (default: root)',
+ },
+ limit: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Maximum number of files to return (default: 100)',
+ },
+ offset: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Number of files to skip (for pagination)',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ let url = `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects`
+ const queryParams: string[] = []
+
+ if (params.path) {
+ queryParams.push(`prefix=${encodeURIComponent(params.path)}`)
+ }
+
+ if (params.limit) {
+ queryParams.push(`limit=${Number(params.limit)}`)
+ }
+
+ if (params.offset) {
+ queryParams.push(`offset=${Number(params.offset)}`)
+ }
+
+ if (queryParams.length > 0) {
+ url += `?${queryParams.join('&')}`
+ }
+
+ return url
+ },
+ method: 'GET',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge storage list response: ${parseError}`)
+ }
+
+ const fileCount = Array.isArray(data) ? data.length : 0
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully listed ${fileCount} file${fileCount === 1 ? '' : 's'} from storage`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of file objects with metadata' },
+ },
+}
diff --git a/apps/sim/tools/insforge/storage_upload.ts b/apps/sim/tools/insforge/storage_upload.ts
new file mode 100644
index 0000000000..a37bf6c0b2
--- /dev/null
+++ b/apps/sim/tools/insforge/storage_upload.ts
@@ -0,0 +1,115 @@
+import type {
+ InsForgeStorageUploadParams,
+ InsForgeStorageUploadResponse,
+} from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const storageUploadTool: ToolConfig<
+ InsForgeStorageUploadParams,
+ InsForgeStorageUploadResponse
+> = {
+ id: 'insforge_storage_upload',
+ name: 'InsForge Storage Upload',
+ description: 'Upload a file to an InsForge storage bucket',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ bucket: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the storage bucket',
+ },
+ path: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The path where the file will be stored (e.g., "folder/file.jpg")',
+ },
+ fileContent: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The file content (base64 encoded for binary files, or plain text)',
+ },
+ contentType: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'MIME type of the file (e.g., "image/jpeg", "text/plain")',
+ },
+ upsert: {
+ type: 'boolean',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'If true, overwrites existing file (default: false)',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}`
+ },
+ method: 'POST',
+ headers: (params) => {
+ const headers: Record = {
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ }
+
+ if (params.contentType) {
+ headers['Content-Type'] = params.contentType
+ }
+
+ if (params.upsert) {
+ headers['x-upsert'] = 'true'
+ }
+
+ return headers
+ },
+ body: (params) => {
+ return {
+ content: params.fileContent,
+ }
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge storage upload response: ${parseError}`)
+ }
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully uploaded file to storage',
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: {
+ type: 'object',
+ description: 'Upload result including file path and metadata',
+ },
+ },
+}
diff --git a/apps/sim/tools/insforge/types.ts b/apps/sim/tools/insforge/types.ts
new file mode 100644
index 0000000000..24d8c35529
--- /dev/null
+++ b/apps/sim/tools/insforge/types.ts
@@ -0,0 +1,198 @@
+import type { ToolResponse } from '@/tools/types'
+
+// Database Query types
+export interface InsForgeQueryParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ filter?: string
+ orderBy?: string
+ limit?: number
+}
+
+export interface InsForgeGetRowParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ filter: string
+}
+
+export interface InsForgeInsertParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ data: Record | Record[]
+}
+
+export interface InsForgeUpdateParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ filter: string
+ data: Record
+}
+
+export interface InsForgeDeleteParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ filter: string
+}
+
+export interface InsForgeUpsertParams {
+ apiKey: string
+ baseUrl: string
+ table: string
+ data: Record | Record[]
+}
+
+// Base response type
+export interface InsForgeBaseResponse extends ToolResponse {
+ output: {
+ message: string
+ results: Record[]
+ }
+ error?: string
+}
+
+export type InsForgeQueryResponse = InsForgeBaseResponse
+export type InsForgeGetRowResponse = InsForgeBaseResponse
+export type InsForgeInsertResponse = InsForgeBaseResponse
+export type InsForgeUpdateResponse = InsForgeBaseResponse
+export type InsForgeDeleteResponse = InsForgeBaseResponse
+export type InsForgeUpsertResponse = InsForgeBaseResponse
+
+// Storage types
+export interface InsForgeStorageUploadParams {
+ apiKey: string
+ baseUrl: string
+ bucket: string
+ path: string
+ fileContent: string
+ contentType?: string
+ upsert?: boolean
+}
+
+export type InsForgeStorageUploadResponse = InsForgeBaseResponse
+
+export interface InsForgeStorageDownloadParams {
+ apiKey: string
+ baseUrl: string
+ bucket: string
+ path: string
+ fileName?: string
+}
+
+export interface InsForgeStorageDownloadResponse extends ToolResponse {
+ output: {
+ file: {
+ name: string
+ mimeType: string
+ data: string | Buffer
+ size: number
+ }
+ }
+ error?: string
+}
+
+export interface InsForgeStorageListParams {
+ apiKey: string
+ baseUrl: string
+ bucket: string
+ path?: string
+ limit?: number
+ offset?: number
+}
+
+export type InsForgeStorageListResponse = InsForgeBaseResponse
+
+export interface InsForgeStorageDeleteParams {
+ apiKey: string
+ baseUrl: string
+ bucket: string
+ path: string
+}
+
+export type InsForgeStorageDeleteResponse = InsForgeBaseResponse
+
+// Functions types
+export interface InsForgeInvokeParams {
+ apiKey: string
+ baseUrl: string
+ functionName: string
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
+ body?: Record
+}
+
+export type InsForgeInvokeResponse = InsForgeBaseResponse
+
+// AI Completion types
+export interface InsForgeCompletionParams {
+ apiKey: string
+ baseUrl: string
+ model?: string
+ messages: Array<{
+ role: 'system' | 'user' | 'assistant'
+ content: string
+ }>
+ temperature?: number
+ maxTokens?: number
+}
+
+export interface InsForgeCompletionResponse extends ToolResponse {
+ output: {
+ message: string
+ content: string
+ usage?: {
+ promptTokens: number
+ completionTokens: number
+ totalTokens: number
+ }
+ }
+ error?: string
+}
+
+// AI Vision types
+export interface InsForgeVisionParams {
+ apiKey: string
+ baseUrl: string
+ model?: string
+ prompt: string
+ imageUrl: string
+ maxTokens?: number
+}
+
+export interface InsForgeVisionResponse extends ToolResponse {
+ output: {
+ message: string
+ content: string
+ usage?: {
+ promptTokens: number
+ completionTokens: number
+ totalTokens: number
+ }
+ }
+ error?: string
+}
+
+// AI Image Generation types
+export interface InsForgeImageGenerationParams {
+ apiKey: string
+ baseUrl: string
+ model?: string
+ prompt: string
+ size?: string
+ quality?: string
+ n?: number
+}
+
+export interface InsForgeImageGenerationResponse extends ToolResponse {
+ output: {
+ message: string
+ images: Array<{
+ url: string
+ revisedPrompt?: string
+ }>
+ }
+ error?: string
+}
diff --git a/apps/sim/tools/insforge/update.ts b/apps/sim/tools/insforge/update.ts
new file mode 100644
index 0000000000..d7925c3f9d
--- /dev/null
+++ b/apps/sim/tools/insforge/update.ts
@@ -0,0 +1,105 @@
+import type { InsForgeUpdateParams, InsForgeUpdateResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const updateTool: ToolConfig = {
+ id: 'insforge_update',
+ name: 'InsForge Update',
+ description: 'Update rows in an InsForge database table based on filter criteria',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to update',
+ },
+ filter: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'PostgREST filter to identify rows to update (e.g., "id=eq.123")',
+ },
+ data: {
+ type: 'json',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'Data to update in the matching rows',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+
+ if (params.filter?.trim()) {
+ url += `&${params.filter.trim()}`
+ }
+
+ return url
+ },
+ method: 'PATCH',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ Prefer: 'return=representation',
+ }),
+ body: (params) => params.data,
+ },
+
+ transformResponse: async (response: Response) => {
+ const text = await response.text()
+ let data
+
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+ } else {
+ data = []
+ }
+
+ const updatedCount = Array.isArray(data) ? data.length : 0
+
+ if (updatedCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No rows were updated (no matching records found)',
+ results: data,
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully updated ${updatedCount} row${updatedCount === 1 ? '' : 's'} in InsForge`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of updated records' },
+ },
+}
diff --git a/apps/sim/tools/insforge/upsert.ts b/apps/sim/tools/insforge/upsert.ts
new file mode 100644
index 0000000000..2b12790774
--- /dev/null
+++ b/apps/sim/tools/insforge/upsert.ts
@@ -0,0 +1,97 @@
+import type { InsForgeUpsertParams, InsForgeUpsertResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const upsertTool: ToolConfig = {
+ id: 'insforge_upsert',
+ name: 'InsForge Upsert',
+ description: 'Insert or update data in an InsForge database table (upsert operation)',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ table: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The name of the table to upsert data into',
+ },
+ data: {
+ type: 'json',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The data to upsert (insert or update) - array of objects or a single object',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ Prefer: 'return=representation,resolution=merge-duplicates',
+ }),
+ body: (params) => {
+ const dataToSend =
+ typeof params.data === 'object' && !Array.isArray(params.data) ? [params.data] : params.data
+ return dataToSend
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ const text = await response.text()
+ let data
+
+ if (text?.trim()) {
+ try {
+ data = JSON.parse(text)
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge response: ${parseError}`)
+ }
+ } else {
+ data = []
+ }
+
+ const upsertedCount = Array.isArray(data) ? data.length : 0
+
+ if (upsertedCount === 0) {
+ return {
+ success: true,
+ output: {
+ message: 'No rows were upserted',
+ results: data,
+ },
+ error: undefined,
+ }
+ }
+
+ return {
+ success: true,
+ output: {
+ message: `Successfully upserted ${upsertedCount} row${upsertedCount === 1 ? '' : 's'} in InsForge`,
+ results: data,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ results: { type: 'array', description: 'Array of upserted records' },
+ },
+}
diff --git a/apps/sim/tools/insforge/vision.ts b/apps/sim/tools/insforge/vision.ts
new file mode 100644
index 0000000000..335f7a3220
--- /dev/null
+++ b/apps/sim/tools/insforge/vision.ts
@@ -0,0 +1,126 @@
+import type { InsForgeVisionParams, InsForgeVisionResponse } from '@/tools/insforge/types'
+import type { ToolConfig } from '@/tools/types'
+
+export const visionTool: ToolConfig = {
+ id: 'insforge_vision',
+ name: 'InsForge AI Vision',
+ description: 'Analyze images using InsForge AI vision capabilities',
+ version: '1.0',
+
+ params: {
+ baseUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge backend URL (e.g., https://your-app.insforge.app)',
+ },
+ model: {
+ type: 'string',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'The vision model to use (e.g., "gpt-4o")',
+ },
+ prompt: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The prompt describing what to analyze in the image',
+ },
+ imageUrl: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'URL of the image to analyze',
+ },
+ maxTokens: {
+ type: 'number',
+ required: false,
+ visibility: 'user-or-llm',
+ description: 'Maximum tokens to generate',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Your InsForge anon key or service role key',
+ },
+ },
+
+ request: {
+ url: (params) => {
+ const base = params.baseUrl.replace(/\/$/, '')
+ return `${base}/api/ai/chat/completion`
+ },
+ method: 'POST',
+ headers: (params) => ({
+ apikey: params.apiKey,
+ Authorization: `Bearer ${params.apiKey}`,
+ 'Content-Type': 'application/json',
+ }),
+ body: (params) => {
+ const body: Record = {
+ messages: [
+ {
+ role: 'user',
+ content: [
+ {
+ type: 'text',
+ text: params.prompt,
+ },
+ {
+ type: 'image_url',
+ image_url: {
+ url: params.imageUrl,
+ },
+ },
+ ],
+ },
+ ],
+ }
+
+ if (params.model) {
+ body.model = params.model
+ }
+
+ if (params.maxTokens) {
+ body.max_tokens = params.maxTokens
+ }
+
+ return body
+ },
+ },
+
+ transformResponse: async (response: Response) => {
+ let data
+ try {
+ data = await response.json()
+ } catch (parseError) {
+ throw new Error(`Failed to parse InsForge AI vision response: ${parseError}`)
+ }
+
+ const content = data?.choices?.[0]?.message?.content || ''
+ const usage = data?.usage
+
+ return {
+ success: true,
+ output: {
+ message: 'Successfully analyzed image',
+ content,
+ usage: usage
+ ? {
+ promptTokens: usage.prompt_tokens,
+ completionTokens: usage.completion_tokens,
+ totalTokens: usage.total_tokens,
+ }
+ : undefined,
+ },
+ error: undefined,
+ }
+ },
+
+ outputs: {
+ message: { type: 'string', description: 'Operation status message' },
+ content: { type: 'string', description: 'Analysis result text' },
+ usage: { type: 'json', description: 'Token usage statistics' },
+ },
+}
diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts
index 88197c9157..49dc68570b 100644
--- a/apps/sim/tools/registry.ts
+++ b/apps/sim/tools/registry.ts
@@ -430,6 +430,22 @@ import {
incidentioWorkflowsShowTool,
incidentioWorkflowsUpdateTool,
} from '@/tools/incidentio'
+import {
+ insforgeCompletionTool,
+ insforgeDeleteTool,
+ insforgeGetRowTool,
+ insforgeImageGenerationTool,
+ insforgeInsertTool,
+ insforgeInvokeTool,
+ insforgeQueryTool,
+ insforgeStorageDeleteTool,
+ insforgeStorageDownloadTool,
+ insforgeStorageListTool,
+ insforgeStorageUploadTool,
+ insforgeUpdateTool,
+ insforgeUpsertTool,
+ insforgeVisionTool,
+} from '@/tools/insforge'
import {
intercomCreateCompanyTool,
intercomCreateContactTool,
@@ -2600,4 +2616,18 @@ export const tools: Record = {
spotify_set_repeat: spotifySetRepeatTool,
spotify_set_shuffle: spotifySetShuffleTool,
spotify_transfer_playback: spotifyTransferPlaybackTool,
+ insforge_query: insforgeQueryTool,
+ insforge_get_row: insforgeGetRowTool,
+ insforge_insert: insforgeInsertTool,
+ insforge_update: insforgeUpdateTool,
+ insforge_delete: insforgeDeleteTool,
+ insforge_upsert: insforgeUpsertTool,
+ insforge_storage_upload: insforgeStorageUploadTool,
+ insforge_storage_download: insforgeStorageDownloadTool,
+ insforge_storage_list: insforgeStorageListTool,
+ insforge_storage_delete: insforgeStorageDeleteTool,
+ insforge_invoke: insforgeInvokeTool,
+ insforge_completion: insforgeCompletionTool,
+ insforge_vision: insforgeVisionTool,
+ insforge_image_generation: insforgeImageGenerationTool,
}