From 3f21664107fdad27c8140656b850765a473fc519 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sat, 10 Jan 2026 20:40:06 -0800 Subject: [PATCH 01/10] feat(tools): add InsForge backend-as-a-service integration Add InsForge block with 14 tools supporting: - Database operations: query, get_row, insert, update, delete, upsert - Storage management: upload, download, list, delete files - Serverless functions: invoke - AI capabilities: chat completion, vision, image generation Includes block definition, tool implementations, icon, registry updates, and documentation. Co-Authored-By: Claude Opus 4.5 --- apps/docs/content/docs/en/tools/insforge.mdx | 349 ++++++++++++ apps/sim/blocks/blocks/insforge.ts | 560 +++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 49 ++ apps/sim/tools/insforge/completion.ts | 114 ++++ apps/sim/tools/insforge/delete.ts | 101 ++++ apps/sim/tools/insforge/get_row.ts | 94 ++++ apps/sim/tools/insforge/image_generation.ts | 125 +++++ apps/sim/tools/insforge/index.ts | 29 + apps/sim/tools/insforge/insert.ts | 97 ++++ apps/sim/tools/insforge/invoke.ts | 82 +++ apps/sim/tools/insforge/query.ts | 118 ++++ apps/sim/tools/insforge/storage_delete.ts | 85 +++ apps/sim/tools/insforge/storage_download.ts | 124 ++++ apps/sim/tools/insforge/storage_list.ts | 103 ++++ apps/sim/tools/insforge/storage_upload.ts | 115 ++++ apps/sim/tools/insforge/types.ts | 197 +++++++ apps/sim/tools/insforge/update.ts | 105 ++++ apps/sim/tools/insforge/upsert.ts | 97 ++++ apps/sim/tools/insforge/vision.ts | 126 +++++ apps/sim/tools/registry.ts | 30 + 21 files changed, 2702 insertions(+) create mode 100644 apps/docs/content/docs/en/tools/insforge.mdx create mode 100644 apps/sim/blocks/blocks/insforge.ts create mode 100644 apps/sim/tools/insforge/completion.ts create mode 100644 apps/sim/tools/insforge/delete.ts create mode 100644 apps/sim/tools/insforge/get_row.ts create mode 100644 apps/sim/tools/insforge/image_generation.ts create mode 100644 apps/sim/tools/insforge/index.ts create mode 100644 apps/sim/tools/insforge/insert.ts create mode 100644 apps/sim/tools/insforge/invoke.ts create mode 100644 apps/sim/tools/insforge/query.ts create mode 100644 apps/sim/tools/insforge/storage_delete.ts create mode 100644 apps/sim/tools/insforge/storage_download.ts create mode 100644 apps/sim/tools/insforge/storage_list.ts create mode 100644 apps/sim/tools/insforge/storage_upload.ts create mode 100644 apps/sim/tools/insforge/types.ts create mode 100644 apps/sim/tools/insforge/update.ts create mode 100644 apps/sim/tools/insforge/upsert.ts create mode 100644 apps/sim/tools/insforge/vision.ts 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..cd3bf39e8f --- /dev/null +++ b/apps/sim/blocks/blocks/insforge.ts @@ -0,0 +1,560 @@ +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: '#6366F1', + 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: 'paths', + title: 'File Paths (JSON array)', + type: 'code', + placeholder: '["folder/file1.jpg", "folder/file2.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, paths, 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 paths array for storage delete + let parsedPaths + if (paths && typeof paths === 'string' && paths.trim()) { + try { + parsedPaths = JSON.parse(paths) + } catch (parseError) { + const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error' + throw new Error(`Invalid paths format: ${errorMsg}`) + } + } else if (paths && Array.isArray(paths)) { + parsedPaths = paths + } + + // 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 (parsedPaths !== undefined) { + result.paths = parsedPaths + } + + 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..9c4849e2a4 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4288,3 +4288,52 @@ 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..be39a6b85e --- /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}/ai/v1/chat/completions` + }, + 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..b270f28850 --- /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}/rest/v1/${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..831f7e472c --- /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}/rest/v1/${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..0e48dbb99c --- /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}/ai/v1/images/generations` + }, + 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..972ebd23d3 --- /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}/rest/v1/${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..3148812e2d --- /dev/null +++ b/apps/sim/tools/insforge/invoke.ts @@ -0,0 +1,82 @@ +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', + }, + 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/v1/${params.functionName}` + }, + method: '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..baf3088403 --- /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}/rest/v1/${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..489c184334 --- /dev/null +++ b/apps/sim/tools/insforge/storage_delete.ts @@ -0,0 +1,85 @@ +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 files 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', + }, + paths: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Array of file paths to delete (e.g., ["folder/file1.jpg", "folder/file2.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}/storage/v1/object/${params.bucket}` + }, + method: 'DELETE', + headers: (params) => ({ + apikey: params.apiKey, + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + return { + prefixes: params.paths, + } + }, + }, + + transformResponse: async (response: Response) => { + let data + try { + data = await response.json() + } catch (parseError) { + throw new Error(`Failed to parse InsForge storage delete response: ${parseError}`) + } + + const deletedCount = Array.isArray(data) ? data.length : 0 + + return { + success: true, + output: { + message: `Successfully deleted ${deletedCount} file${deletedCount === 1 ? '' : 's'} from storage`, + results: data, + }, + error: undefined, + } + }, + + outputs: { + message: { type: 'string', description: 'Operation status message' }, + results: { type: 'array', description: 'Array of deleted file objects' }, + }, +} diff --git a/apps/sim/tools/insforge/storage_download.ts b/apps/sim/tools/insforge/storage_download.ts new file mode 100644 index 0000000000..ee5613a265 --- /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}/storage/v1/object/${params.bucket}/${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..5916e4ce3a --- /dev/null +++ b/apps/sim/tools/insforge/storage_list.ts @@ -0,0 +1,103 @@ +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(/\/$/, '') + return `${base}/storage/v1/object/list/${params.bucket}` + }, + method: 'POST', + headers: (params) => ({ + apikey: params.apiKey, + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + + if (params.path) { + body.prefix = params.path + } + + if (params.limit) { + body.limit = Number(params.limit) + } + + if (params.offset) { + body.offset = Number(params.offset) + } + + return body + }, + }, + + 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..1b81b1cd0c --- /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}/storage/v1/object/${params.bucket}/${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..222a96effe --- /dev/null +++ b/apps/sim/tools/insforge/types.ts @@ -0,0 +1,197 @@ +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 + paths: string[] +} + +export type InsForgeStorageDeleteResponse = InsForgeBaseResponse + +// Functions types +export interface InsForgeInvokeParams { + apiKey: string + baseUrl: string + functionName: string + 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..6e9416da6a --- /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}/rest/v1/${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..cb14435b71 --- /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}/rest/v1/${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..d9ba090731 --- /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}/ai/v1/chat/completions` + }, + 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, } From 48cf5e3e521c75c7d86b92d7c9be3ce07f3dd4a7 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sat, 10 Jan 2026 20:45:22 -0800 Subject: [PATCH 02/10] fix(insforge): use official InsForge logo and black background Co-Authored-By: Claude Opus 4.5 --- apps/sim/blocks/blocks/insforge.ts | 2 +- apps/sim/components/icons.tsx | 38 ++++-------------------------- 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/apps/sim/blocks/blocks/insforge.ts b/apps/sim/blocks/blocks/insforge.ts index cd3bf39e8f..1d7de553fa 100644 --- a/apps/sim/blocks/blocks/insforge.ts +++ b/apps/sim/blocks/blocks/insforge.ts @@ -11,7 +11,7 @@ export const InsForgeBlock: BlockConfig = { '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: '#6366F1', + bgColor: '#000000', icon: InsForgeIcon, subBlocks: [ { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 9c4849e2a4..dbfbb24b02 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4295,45 +4295,15 @@ export function InsForgeIcon(props: SVGProps) { {...props} width='24' height='24' - viewBox='0 0 24 24' + viewBox='0 0 240 240' fill='none' xmlns='http://www.w3.org/2000/svg' > - - - - + ) } From 9ad636800a48647db5467f667690c0211b2cfdd7 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sat, 10 Jan 2026 21:48:16 -0800 Subject: [PATCH 03/10] fix(insforge): use correct InsForge API paths Updated all InsForge tool API endpoints to match the actual backend routes: - Database: /api/database/:tableName (was /rest/v1/:tableName) - Storage: /api/storage/buckets/:bucket/objects/:key - Functions: /functions/:slug - AI Chat: /api/ai/chat/completion - AI Image: /api/ai/image/generation Also updated storage_delete to use single file deletion instead of bulk delete to match InsForge API design. Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/completion.ts | 2 +- apps/sim/tools/insforge/delete.ts | 2 +- apps/sim/tools/insforge/get_row.ts | 2 +- apps/sim/tools/insforge/image_generation.ts | 2 +- apps/sim/tools/insforge/insert.ts | 2 +- apps/sim/tools/insforge/invoke.ts | 2 +- apps/sim/tools/insforge/query.ts | 2 +- apps/sim/tools/insforge/storage_delete.ts | 33 +++++++++++---------- apps/sim/tools/insforge/storage_download.ts | 2 +- apps/sim/tools/insforge/storage_list.ts | 29 +++++++++--------- apps/sim/tools/insforge/storage_upload.ts | 2 +- apps/sim/tools/insforge/types.ts | 2 +- apps/sim/tools/insforge/update.ts | 2 +- apps/sim/tools/insforge/upsert.ts | 2 +- apps/sim/tools/insforge/vision.ts | 2 +- 15 files changed, 45 insertions(+), 43 deletions(-) diff --git a/apps/sim/tools/insforge/completion.ts b/apps/sim/tools/insforge/completion.ts index be39a6b85e..a2e3e6cbec 100644 --- a/apps/sim/tools/insforge/completion.ts +++ b/apps/sim/tools/insforge/completion.ts @@ -49,7 +49,7 @@ export const completionTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/ai/v1/chat/completions` + return `${base}/api/ai/chat/completion` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/delete.ts b/apps/sim/tools/insforge/delete.ts index b270f28850..6cb8035b4c 100644 --- a/apps/sim/tools/insforge/delete.ts +++ b/apps/sim/tools/insforge/delete.ts @@ -37,7 +37,7 @@ export const deleteTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/rest/v1/${params.table}?select=*` + let url = `${base}/api/database/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/get_row.ts b/apps/sim/tools/insforge/get_row.ts index 831f7e472c..e4711d2dbd 100644 --- a/apps/sim/tools/insforge/get_row.ts +++ b/apps/sim/tools/insforge/get_row.ts @@ -37,7 +37,7 @@ export const getRowTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/rest/v1/${params.table}?select=*` + let url = `${base}/api/database/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/image_generation.ts b/apps/sim/tools/insforge/image_generation.ts index 0e48dbb99c..0bed892ad7 100644 --- a/apps/sim/tools/insforge/image_generation.ts +++ b/apps/sim/tools/insforge/image_generation.ts @@ -61,7 +61,7 @@ export const imageGenerationTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/ai/v1/images/generations` + return `${base}/api/ai/image/generation` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/insert.ts b/apps/sim/tools/insforge/insert.ts index 972ebd23d3..c19bee1c16 100644 --- a/apps/sim/tools/insforge/insert.ts +++ b/apps/sim/tools/insforge/insert.ts @@ -37,7 +37,7 @@ export const insertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/rest/v1/${params.table}?select=*` + return `${base}/api/database/${params.table}?select=*` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/invoke.ts b/apps/sim/tools/insforge/invoke.ts index 3148812e2d..aa382fc7ac 100644 --- a/apps/sim/tools/insforge/invoke.ts +++ b/apps/sim/tools/insforge/invoke.ts @@ -37,7 +37,7 @@ export const invokeTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/functions/v1/${params.functionName}` + return `${base}/functions/${params.functionName}` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/query.ts b/apps/sim/tools/insforge/query.ts index baf3088403..73cfa2ac57 100644 --- a/apps/sim/tools/insforge/query.ts +++ b/apps/sim/tools/insforge/query.ts @@ -49,7 +49,7 @@ export const queryTool: ToolConfig = request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/rest/v1/${params.table}?select=*` + let url = `${base}/api/database/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/storage_delete.ts b/apps/sim/tools/insforge/storage_delete.ts index 489c184334..0ab4fe899b 100644 --- a/apps/sim/tools/insforge/storage_delete.ts +++ b/apps/sim/tools/insforge/storage_delete.ts @@ -10,7 +10,7 @@ export const storageDeleteTool: ToolConfig< > = { id: 'insforge_storage_delete', name: 'InsForge Storage Delete', - description: 'Delete files from an InsForge storage bucket', + description: 'Delete a file from an InsForge storage bucket', version: '1.0', params: { @@ -26,11 +26,11 @@ export const storageDeleteTool: ToolConfig< visibility: 'user-or-llm', description: 'The name of the storage bucket', }, - paths: { - type: 'array', + path: { + type: 'string', required: true, visibility: 'user-or-llm', - description: 'Array of file paths to delete (e.g., ["folder/file1.jpg", "folder/file2.jpg"])', + description: 'The file path to delete (e.g., "folder/file.jpg")', }, apiKey: { type: 'string', @@ -43,35 +43,36 @@ export const storageDeleteTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/storage/v1/object/${params.bucket}` + return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` }, method: 'DELETE', headers: (params) => ({ apikey: params.apiKey, Authorization: `Bearer ${params.apiKey}`, - 'Content-Type': 'application/json', }), - body: (params) => { - return { - prefixes: params.paths, - } - }, }, transformResponse: async (response: Response) => { let data try { - data = await response.json() + 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}`) } - const deletedCount = Array.isArray(data) ? data.length : 0 - return { success: true, output: { - message: `Successfully deleted ${deletedCount} file${deletedCount === 1 ? '' : 's'} from storage`, + message: 'Successfully deleted file from storage', results: data, }, error: undefined, @@ -80,6 +81,6 @@ export const storageDeleteTool: ToolConfig< outputs: { message: { type: 'string', description: 'Operation status message' }, - results: { type: 'array', description: 'Array of deleted file objects' }, + 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 index ee5613a265..9137f93714 100644 --- a/apps/sim/tools/insforge/storage_download.ts +++ b/apps/sim/tools/insforge/storage_download.ts @@ -52,7 +52,7 @@ export const storageDownloadTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/storage/v1/object/${params.bucket}/${params.path}` + return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` }, method: 'GET', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/storage_list.ts b/apps/sim/tools/insforge/storage_list.ts index 5916e4ce3a..fb9243bf7a 100644 --- a/apps/sim/tools/insforge/storage_list.ts +++ b/apps/sim/tools/insforge/storage_list.ts @@ -49,31 +49,32 @@ export const storageListTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/storage/v1/object/list/${params.bucket}` - }, - method: 'POST', - headers: (params) => ({ - apikey: params.apiKey, - Authorization: `Bearer ${params.apiKey}`, - 'Content-Type': 'application/json', - }), - body: (params) => { - const body: Record = {} + let url = `${base}/api/storage/buckets/${params.bucket}/objects` + const queryParams: string[] = [] if (params.path) { - body.prefix = params.path + queryParams.push(`prefix=${encodeURIComponent(params.path)}`) } if (params.limit) { - body.limit = Number(params.limit) + queryParams.push(`limit=${Number(params.limit)}`) } if (params.offset) { - body.offset = Number(params.offset) + queryParams.push(`offset=${Number(params.offset)}`) } - return body + if (queryParams.length > 0) { + url += `?${queryParams.join('&')}` + } + + return url }, + method: 'GET', + headers: (params) => ({ + apikey: params.apiKey, + Authorization: `Bearer ${params.apiKey}`, + }), }, transformResponse: async (response: Response) => { diff --git a/apps/sim/tools/insforge/storage_upload.ts b/apps/sim/tools/insforge/storage_upload.ts index 1b81b1cd0c..c6e84e34ab 100644 --- a/apps/sim/tools/insforge/storage_upload.ts +++ b/apps/sim/tools/insforge/storage_upload.ts @@ -61,7 +61,7 @@ export const storageUploadTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/storage/v1/object/${params.bucket}/${params.path}` + return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/insforge/types.ts b/apps/sim/tools/insforge/types.ts index 222a96effe..d7704f7054 100644 --- a/apps/sim/tools/insforge/types.ts +++ b/apps/sim/tools/insforge/types.ts @@ -110,7 +110,7 @@ export interface InsForgeStorageDeleteParams { apiKey: string baseUrl: string bucket: string - paths: string[] + path: string } export type InsForgeStorageDeleteResponse = InsForgeBaseResponse diff --git a/apps/sim/tools/insforge/update.ts b/apps/sim/tools/insforge/update.ts index 6e9416da6a..bd682b81d2 100644 --- a/apps/sim/tools/insforge/update.ts +++ b/apps/sim/tools/insforge/update.ts @@ -43,7 +43,7 @@ export const updateTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/rest/v1/${params.table}?select=*` + let url = `${base}/api/database/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/upsert.ts b/apps/sim/tools/insforge/upsert.ts index cb14435b71..545d354991 100644 --- a/apps/sim/tools/insforge/upsert.ts +++ b/apps/sim/tools/insforge/upsert.ts @@ -37,7 +37,7 @@ export const upsertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/rest/v1/${params.table}?select=*` + return `${base}/api/database/${params.table}?select=*` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/vision.ts b/apps/sim/tools/insforge/vision.ts index d9ba090731..335f7a3220 100644 --- a/apps/sim/tools/insforge/vision.ts +++ b/apps/sim/tools/insforge/vision.ts @@ -49,7 +49,7 @@ export const visionTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/ai/v1/chat/completions` + return `${base}/api/ai/chat/completion` }, method: 'POST', headers: (params) => ({ From b51f8b5b1a541a86d360dbe515d00ba67154e2c7 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sat, 10 Jan 2026 22:32:13 -0800 Subject: [PATCH 04/10] fix(insforge): use correct /api/database/records/ path for data queries The InsForge backend routes data queries through /api/database/records/:tableName, not /api/database/:tableName. Updated all database tools accordingly. Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/delete.ts | 2 +- apps/sim/tools/insforge/get_row.ts | 2 +- apps/sim/tools/insforge/insert.ts | 2 +- apps/sim/tools/insforge/query.ts | 2 +- apps/sim/tools/insforge/update.ts | 2 +- apps/sim/tools/insforge/upsert.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/sim/tools/insforge/delete.ts b/apps/sim/tools/insforge/delete.ts index 6cb8035b4c..063069ee43 100644 --- a/apps/sim/tools/insforge/delete.ts +++ b/apps/sim/tools/insforge/delete.ts @@ -37,7 +37,7 @@ export const deleteTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/${params.table}?select=*` + let url = `${base}/api/database/records/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/get_row.ts b/apps/sim/tools/insforge/get_row.ts index e4711d2dbd..374fb5908c 100644 --- a/apps/sim/tools/insforge/get_row.ts +++ b/apps/sim/tools/insforge/get_row.ts @@ -37,7 +37,7 @@ export const getRowTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/${params.table}?select=*` + let url = `${base}/api/database/records/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/insert.ts b/apps/sim/tools/insforge/insert.ts index c19bee1c16..7e69e3d74a 100644 --- a/apps/sim/tools/insforge/insert.ts +++ b/apps/sim/tools/insforge/insert.ts @@ -37,7 +37,7 @@ export const insertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/database/${params.table}?select=*` + return `${base}/api/database/records/${params.table}?select=*` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/query.ts b/apps/sim/tools/insforge/query.ts index 73cfa2ac57..f72fb1db6e 100644 --- a/apps/sim/tools/insforge/query.ts +++ b/apps/sim/tools/insforge/query.ts @@ -49,7 +49,7 @@ export const queryTool: ToolConfig = request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/${params.table}?select=*` + let url = `${base}/api/database/records/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/update.ts b/apps/sim/tools/insforge/update.ts index bd682b81d2..7ed618d529 100644 --- a/apps/sim/tools/insforge/update.ts +++ b/apps/sim/tools/insforge/update.ts @@ -43,7 +43,7 @@ export const updateTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/${params.table}?select=*` + let url = `${base}/api/database/records/${params.table}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/upsert.ts b/apps/sim/tools/insforge/upsert.ts index 545d354991..dc3e7e514a 100644 --- a/apps/sim/tools/insforge/upsert.ts +++ b/apps/sim/tools/insforge/upsert.ts @@ -37,7 +37,7 @@ export const upsertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/database/${params.table}?select=*` + return `${base}/api/database/records/${params.table}?select=*` }, method: 'POST', headers: (params) => ({ From 31e8b5585aa063d5722780b1cd82bd4a8c818d81 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 15:58:24 -0800 Subject: [PATCH 05/10] fix(insforge): use singular path param for storage_delete block config Block config was using 'paths' (array) but tool expects 'path' (string). Co-Authored-By: Claude Opus 4.5 --- apps/sim/blocks/blocks/insforge.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sim/blocks/blocks/insforge.ts b/apps/sim/blocks/blocks/insforge.ts index 1d7de553fa..15f3b7c53d 100644 --- a/apps/sim/blocks/blocks/insforge.ts +++ b/apps/sim/blocks/blocks/insforge.ts @@ -228,10 +228,10 @@ export const InsForgeBlock: BlockConfig = { }, // Storage Delete fields { - id: 'paths', - title: 'File Paths (JSON array)', - type: 'code', - placeholder: '["folder/file1.jpg", "folder/file2.jpg"]', + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: 'folder/file.jpg', condition: { field: 'operation', value: 'storage_delete' }, required: true, }, From c0c71fe197c81f5377d4d2ec2ab92d6dde1cfc52 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 16:00:01 -0800 Subject: [PATCH 06/10] fix(insforge): remove unused paths array parsing logic The storage_delete tool uses singular 'path', not 'paths' array. Co-Authored-By: Claude Opus 4.5 --- apps/sim/blocks/blocks/insforge.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/sim/blocks/blocks/insforge.ts b/apps/sim/blocks/blocks/insforge.ts index 15f3b7c53d..11e2d61ea7 100644 --- a/apps/sim/blocks/blocks/insforge.ts +++ b/apps/sim/blocks/blocks/insforge.ts @@ -414,7 +414,7 @@ export const InsForgeBlock: BlockConfig = { }, params: (params) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { operation, data, paths, body, messages, upsert, ...rest } = params + const { operation, data, body, messages, upsert, ...rest } = params // Parse JSON data if it's a string let parsedData @@ -429,19 +429,6 @@ export const InsForgeBlock: BlockConfig = { parsedData = data } - // Handle paths array for storage delete - let parsedPaths - if (paths && typeof paths === 'string' && paths.trim()) { - try { - parsedPaths = JSON.parse(paths) - } catch (parseError) { - const errorMsg = parseError instanceof Error ? parseError.message : 'Unknown JSON error' - throw new Error(`Invalid paths format: ${errorMsg}`) - } - } else if (paths && Array.isArray(paths)) { - parsedPaths = paths - } - // Handle body for function invoke let parsedBody if (body && typeof body === 'string' && body.trim()) { @@ -478,10 +465,6 @@ export const InsForgeBlock: BlockConfig = { result.data = parsedData } - if (parsedPaths !== undefined) { - result.paths = parsedPaths - } - if (parsedBody !== undefined) { result.body = parsedBody } From 3b833876add961488e7a42e451d27cb0f348bb7d Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 16:00:44 -0800 Subject: [PATCH 07/10] feat(insforge): add method parameter to invoke function tool Supports GET, POST, PUT, DELETE (default: POST) as documented. Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/invoke.ts | 8 +++++++- apps/sim/tools/insforge/types.ts | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/insforge/invoke.ts b/apps/sim/tools/insforge/invoke.ts index aa382fc7ac..5b0a46bc8b 100644 --- a/apps/sim/tools/insforge/invoke.ts +++ b/apps/sim/tools/insforge/invoke.ts @@ -20,6 +20,12 @@ export const invokeTool: ToolConfig (params.method as 'GET' | 'POST' | 'PUT' | 'DELETE') || 'POST', headers: (params) => ({ apikey: params.apiKey, Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/insforge/types.ts b/apps/sim/tools/insforge/types.ts index d7704f7054..24d8c35529 100644 --- a/apps/sim/tools/insforge/types.ts +++ b/apps/sim/tools/insforge/types.ts @@ -120,6 +120,7 @@ export interface InsForgeInvokeParams { apiKey: string baseUrl: string functionName: string + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' body?: Record } From 4cc5633e7897e8fe114cb4d13ae9d40e03e90afd Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 16:01:45 -0800 Subject: [PATCH 08/10] fix(insforge): encode table names in URL paths Use encodeURIComponent to handle special characters in table names. Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/delete.ts | 2 +- apps/sim/tools/insforge/get_row.ts | 2 +- apps/sim/tools/insforge/insert.ts | 2 +- apps/sim/tools/insforge/query.ts | 2 +- apps/sim/tools/insforge/update.ts | 2 +- apps/sim/tools/insforge/upsert.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/sim/tools/insforge/delete.ts b/apps/sim/tools/insforge/delete.ts index 063069ee43..c9b1404f30 100644 --- a/apps/sim/tools/insforge/delete.ts +++ b/apps/sim/tools/insforge/delete.ts @@ -37,7 +37,7 @@ export const deleteTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/records/${params.table}?select=*` + let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/get_row.ts b/apps/sim/tools/insforge/get_row.ts index 374fb5908c..88751592ee 100644 --- a/apps/sim/tools/insforge/get_row.ts +++ b/apps/sim/tools/insforge/get_row.ts @@ -37,7 +37,7 @@ export const getRowTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/records/${params.table}?select=*` + let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/insert.ts b/apps/sim/tools/insforge/insert.ts index 7e69e3d74a..40f87173a4 100644 --- a/apps/sim/tools/insforge/insert.ts +++ b/apps/sim/tools/insforge/insert.ts @@ -37,7 +37,7 @@ export const insertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/database/records/${params.table}?select=*` + return `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` }, method: 'POST', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/query.ts b/apps/sim/tools/insforge/query.ts index f72fb1db6e..e7435ce645 100644 --- a/apps/sim/tools/insforge/query.ts +++ b/apps/sim/tools/insforge/query.ts @@ -49,7 +49,7 @@ export const queryTool: ToolConfig = request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/records/${params.table}?select=*` + let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/update.ts b/apps/sim/tools/insforge/update.ts index 7ed618d529..d7925c3f9d 100644 --- a/apps/sim/tools/insforge/update.ts +++ b/apps/sim/tools/insforge/update.ts @@ -43,7 +43,7 @@ export const updateTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/database/records/${params.table}?select=*` + let url = `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` if (params.filter?.trim()) { url += `&${params.filter.trim()}` diff --git a/apps/sim/tools/insforge/upsert.ts b/apps/sim/tools/insforge/upsert.ts index dc3e7e514a..2b12790774 100644 --- a/apps/sim/tools/insforge/upsert.ts +++ b/apps/sim/tools/insforge/upsert.ts @@ -37,7 +37,7 @@ export const upsertTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/database/records/${params.table}?select=*` + return `${base}/api/database/records/${encodeURIComponent(params.table)}?select=*` }, method: 'POST', headers: (params) => ({ From 5705d1340ad889bdd40a862adf638b2917dc6b95 Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 16:02:43 -0800 Subject: [PATCH 09/10] fix(insforge): encode bucket and path in storage URL paths Use encodeURIComponent to handle special characters. Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/storage_delete.ts | 2 +- apps/sim/tools/insforge/storage_download.ts | 2 +- apps/sim/tools/insforge/storage_list.ts | 2 +- apps/sim/tools/insforge/storage_upload.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/insforge/storage_delete.ts b/apps/sim/tools/insforge/storage_delete.ts index 0ab4fe899b..e97c424be9 100644 --- a/apps/sim/tools/insforge/storage_delete.ts +++ b/apps/sim/tools/insforge/storage_delete.ts @@ -43,7 +43,7 @@ export const storageDeleteTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` + return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}` }, method: 'DELETE', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/storage_download.ts b/apps/sim/tools/insforge/storage_download.ts index 9137f93714..888330a3ab 100644 --- a/apps/sim/tools/insforge/storage_download.ts +++ b/apps/sim/tools/insforge/storage_download.ts @@ -52,7 +52,7 @@ export const storageDownloadTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` + return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}` }, method: 'GET', headers: (params) => ({ diff --git a/apps/sim/tools/insforge/storage_list.ts b/apps/sim/tools/insforge/storage_list.ts index fb9243bf7a..fc01059fb8 100644 --- a/apps/sim/tools/insforge/storage_list.ts +++ b/apps/sim/tools/insforge/storage_list.ts @@ -49,7 +49,7 @@ export const storageListTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - let url = `${base}/api/storage/buckets/${params.bucket}/objects` + let url = `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects` const queryParams: string[] = [] if (params.path) { diff --git a/apps/sim/tools/insforge/storage_upload.ts b/apps/sim/tools/insforge/storage_upload.ts index c6e84e34ab..a37bf6c0b2 100644 --- a/apps/sim/tools/insforge/storage_upload.ts +++ b/apps/sim/tools/insforge/storage_upload.ts @@ -61,7 +61,7 @@ export const storageUploadTool: ToolConfig< request: { url: (params) => { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/api/storage/buckets/${params.bucket}/objects/${params.path}` + return `${base}/api/storage/buckets/${encodeURIComponent(params.bucket)}/objects/${encodeURIComponent(params.path)}` }, method: 'POST', headers: (params) => { From 7135f2071013e6957eac45ef928022158797f3dc Mon Sep 17 00:00:00 2001 From: yaowenc2 Date: Sun, 11 Jan 2026 16:04:08 -0800 Subject: [PATCH 10/10] fix(insforge): encode function name in invoke URL path Co-Authored-By: Claude Opus 4.5 --- apps/sim/tools/insforge/invoke.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/insforge/invoke.ts b/apps/sim/tools/insforge/invoke.ts index 5b0a46bc8b..1c4f17c891 100644 --- a/apps/sim/tools/insforge/invoke.ts +++ b/apps/sim/tools/insforge/invoke.ts @@ -43,7 +43,7 @@ export const invokeTool: ToolConfig { const base = params.baseUrl.replace(/\/$/, '') - return `${base}/functions/${params.functionName}` + return `${base}/functions/${encodeURIComponent(params.functionName)}` }, method: (params) => (params.method as 'GET' | 'POST' | 'PUT' | 'DELETE') || 'POST', headers: (params) => ({