feat(sheets): added sheet selector for microsoft excel and google sheets tools (#2835)

* feat(sheets): added sheet selector for microsoft excel and google sheets tools

* upgrade generate docs script

* updated tests

* added sheet-selector to tool-input

* added cursor docs
This commit is contained in:
Waleed
2026-01-15 00:01:31 -08:00
committed by GitHub
parent e53538d079
commit 929d0d01fd
47 changed files with 4416 additions and 280 deletions

View File

@@ -163,7 +163,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_search: GoogleIcon,
google_sheets: GoogleSheetsIcon,
google_sheets_v2: GoogleSheetsIcon,
google_slides: GoogleSlidesIcon,
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
@@ -190,7 +190,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
mailgun: MailgunIcon,
mem0: Mem0Icon,
memory: BrainIcon,
microsoft_excel: MicrosoftExcelIcon,
microsoft_excel_v2: MicrosoftExcelIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_teams: MicrosoftTeamsIcon,
mistral_parse: MistralIcon,

View File

@@ -10,6 +10,21 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Asana](https://asana.com/) is a leading work management platform designed to help teams organize, track, and manage their tasks and projects more efficiently. With Asana, individuals and organizations can streamline project planning, delegate responsibilities, monitor progress, and collaborate seamlessly across different workspaces and projects.
With Asana, you can:
- **Create, assign, and update tasks**: Break down your work into actionable items, assign them to team members, and keep everyone on the same page.
- **Organize projects**: Group related tasks into projects, set deadlines, and visualize the flow of work from start to finish.
- **Set priorities and due dates**: Ensure important tasks are completed on time and stay aligned with your project goals.
- **Track progress and completion**: Monitor tasks as they move through various stages and quickly identify roadblocks.
- **Collaborate with your team**: Share notes, attach relevant resources, and communicate updates directly on tasks.
In Sim, the Asana integration enables your agents to programmatically interact with Asana through a suite of flexible tools described below. Your agents can retrieve, create, and update tasks, making it easy to automate project management workflows, synchronize status with other tools, or trigger actions based on Asana task events. Harness these tools to streamline your team's productivity and keep all your projects organized and up to date directly within your Sim projects.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Asana into the workflow. Can read, write, and update tasks.

View File

@@ -11,27 +11,17 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
/>
{/* MANUAL-CONTENT-START:intro */}
[Clay](https://www.clay.com/) is a data enrichment and workflow automation platform that helps teams streamline lead generation, research, and data operations through powerful integrations and flexible inputs.
[Clay](https://www.clay.com/) is a data enrichment and workflow automation platform designed to help teams streamline lead generation, research, and other data operations using powerful integrations and flexible input options.
Learn how to use the Clay Tool in Sim to seamlessly insert data into a Clay workbook through webhook triggers. This tutorial walks you through setting up a webhook, configuring data mapping, and automating real-time updates to your Clay workbooks. Perfect for streamlining lead generation and data enrichment directly from your workflow!
<iframe
width="100%"
height="400"
src="https://www.youtube.com/embed/cx_75X5sI_s"
title="Clay Integration with Sim"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
In Sim, the Clay integration lets your agents seamlessly insert structured data into Clay workbooks via webhook triggers. This makes it easy to collect, enrich, and manage dynamic outputs—such as leads, research summaries, or action items—directly within a collaborative, spreadsheet-like interface.
With Clay, you can:
- **Enrich agent outputs**: Automatically feed your Sim agent data into Clay tables for structured tracking and analysis
- **Trigger workflows via webhooks**: Use Clays webhook support to initiate Sim agent tasks from within Clay
- **Leverage data loops**: Seamlessly iterate over enriched data rows with agents that operate across dynamic datasets
- **Enrich agent outputs**: Automatically feed your Sim agent data into Clay tables for structured tracking and analysis.
- **Trigger workflows via webhooks**: Use Clays webhook support to initiate Sim agent tasks directly from Clay or have agents send data to Clay as part of your workflow.
- **Leverage data loops**: Seamlessly iterate over enriched data rows with agents that operate across dynamic datasets.
In Sim, the Clay integration allows your agents to push structured data into Clay tables via webhooks. This makes it easy to collect, enrich, and manage dynamic outputs such as leads, research summaries, or action items—all in a collaborative, spreadsheet-like interface. Your agents can populate rows in real time, enabling asynchronous workflows where AI-generated insights are captured, reviewed, and used by your team. Whether you're automating research, enriching CRM data, or tracking operational outcomes, Clay becomes a living data layer that interacts intelligently with your agents. By connecting Sim with Clay, you gain a powerful way to operationalize agent results, loop over datasets with precision, and maintain a clean, auditable record of AI-driven work.
The integration supports workflows where your agents populate rows in real time, enabling asynchronous, collaborative work. Whether you're automating research, enriching CRM data, or tracking operational outcomes, Clay becomes a living data layer that interacts intelligently with your agents. By connecting Sim with Clay, you can operationalize agent-generated results, automate dataset processing, and maintain an auditable, up-to-date record of AI-driven work.
{/* MANUAL-CONTENT-END */}

View File

@@ -1,15 +1,35 @@
---
title: Cursor
description: Agent identifier
description: Launch and manage Cursor cloud agents to work on GitHub repositories
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor_v2"
color="#F5F5F5"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so) is an intelligent cloud-based platform that enables you to launch and manage AI agents capable of collaborating on your GitHub repositories. Cursor agents are designed to help automate software development workflows, accelerate code changes, and provide powerful assistance directly within your version control stack.
With Cursor, you can:
- **Launch cloud agents**: Instantly start AI agents to perform tasks on your repositories—ranging from code generation and refactoring to documentation and bug fixing.
- **Collaborate on pull requests and branches**: Agents can work on feature branches, propose changes, and assist with code reviews.
- **Guide and refine AI work**: Provide follow-up instructions to agents, enabling you to iteratively direct their actions and results.
- **Monitor progress and results**: Check agent status, review their output, and inspect conversation threads—all from a unified dashboard or API.
- **Control agent lifecycle**: Start, stop, restart, or archive agents as needed to manage compute resources and workflow states.
- **Integrate with your workflow**: Use the API to connect Cursor agents with CI/CD pipelines, chatbots, or internal tools for automated workflows.
Integrating Cursor into your Sim automations unleashes the power of AI assistance on your software projects. Let agents contribute code, resolve issues, and complete repetitive development tasks so you and your team can focus on higher-level engineering work.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.
## Tools

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,36 @@
---
title: Gmail
description: Gmail message ID
description: Send, read, search, and move Gmail messages or trigger workflows from Gmail events
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="gmail_v2"
color="#F5F5F5"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Gmail](https://mail.google.com/) is one of the worlds most popular and reliable email services, trusted by individuals and organizations to send, receive, and manage messages. Gmail offers a secure, intuitive interface with advanced organization and search capabilities, making it a top choice for personal and professional communication.
Gmail provides a comprehensive suite of features for efficient email management, message filtering, and workflow integration. With its powerful API, Gmail enables developers and platforms to automate common email-related tasks, integrate mailbox activities into broader workflows, and enhance productivity by reducing manual effort.
Key features of Gmail include:
- Email Sending and Receiving: Compose, send, and receive emails reliably and securely
- Message Search and Organization: Advanced search, labels, and filters to easily find and categorize messages
- Conversation Threading: Keeps related messages grouped together for better conversation tracking
- Attachments and Formatting: Support for file attachments, rich formatting, and embedded media
- Integration and Automation: Robust API for integrating with other tools and automating email workflows
In Sim, the Gmail integration allows your agents to interact with your emails programmatically—sending, receiving, searching, and organizing messages as part of powerful AI workflows. Agents can draft emails, trigger processes based on new email arrivals, and automate repetitive email tasks, freeing up time and reducing manual labor. By connecting Sim with Gmail, you can build intelligent agents to manage communications, automate follow-ups, and maintain organized inboxes within your workflows.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Gmail into the workflow. Can send, read, search, and move emails. Can be used in trigger mode to trigger a workflow when a new email is received.
## Tools

View File

@@ -1,15 +1,37 @@
---
title: Google Calendar
description: Event ID
description: Manage Google Calendar events
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_calendar_v2"
color="#F5F5F5"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Calendar](https://calendar.google.com) is Google's widely used online calendar and scheduling service, making it easy to organize meetings, events, reminders, and appointments individually or collaboratively. As a key part of Google Workspace, Google Calendar offers robust tools for managing your schedule, sending invitations, setting event reminders, and sharing calendars with others.
Google Calendar supports feature-rich integrations and automation, allowing users and teams to streamline event management and keep their workflows synchronized. Its API enables programmatic creation, modification, and listing of calendar events, empowering agents and automated workflows to interact with your schedule in real time.
Key features of Google Calendar include:
- **Event Scheduling**: Create one-time or recurring events with rich details like time, location, and guests.
- **Reminders & Notifications**: Automated email and push reminders to ensure you never miss an important event.
- **Sharing & Collaboration**: Share calendars with individuals or groups, manage permissions, and coordinate meetings seamlessly.
- **Integration**: Connect with Gmail, Meet, Docs, and external tools for a unified productivity experience.
- **Time Zone Support**: Schedule meetings across regions with full time zone awareness.
- **Mobile & Multi-Device Access**: Access your calendar from web, mobile, and desktop.
In Sim, the Google Calendar integration allows your agents to read, create, update, and list calendar events as part of automated workflows. This enables powerful scenarios such as syncing meeting information, generating reminders, tracking event changes, coordinating team schedules, and much more. By connecting Sim with Google Calendar, your agents can handle scheduling tasks, manage events intelligently, and keep your whole organization on track without manual intervention.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Google Calendar into the workflow. Can create, read, update, and list calendar events.
## Tools

View File

@@ -1,83 +1,34 @@
---
title: Google Sheets
description: Read, write, and update data
description: Read, write, and update data with sheet selection
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_sheets"
type="google_sheets_v2"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Sheets](https://sheets.google.com) is a powerful cloud-based spreadsheet application that allows users to create, edit, and collaborate on spreadsheets in real-time. As part of Google's productivity suite, Google Sheets offers a versatile platform for data organization, analysis, and visualization with robust formatting, formula, and sharing capabilities.
[Google Sheets](https://www.google.com/sheets/about/) is a powerful, cloud-based spreadsheet platform that allows teams and individuals to create, edit, and collaborate on spreadsheets in real-time. Widely used for data tracking, reporting, and lightweight database needs, Google Sheets seamlessly integrates with many tools and services to empower workflow automation and data-driven operations.
Learn how to integrate the Google Sheets "Read" tool in Sim to effortlessly fetch data from your spreadsheets to integrate into your workflows. This tutorial walks you through connecting Google Sheets, setting up data reads, and using that information to automate processes in real-time. Perfect for syncing live data with your agents.
Google Sheets offers an extensive feature set for managing and analyzing tabular data, supporting everything from basic calculations to complex reporting and collaborative editing. Its robust API and integration capabilities enable automated access, updates, and reporting from agents and external services.
<iframe
width="100%"
height="400"
src="https://www.youtube.com/embed/xxP7MZRuq_0"
title="Use the Google Sheets Read tool in Sim"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
Key features of Google Sheets include:
Discover how to use the Google Sheets "Write" tool in Sim to automatically send data from your workflows to your Google Sheets. This tutorial covers setting up the integration, configuring write operations, and updating your sheets seamlessly as workflows execute. Perfect for maintaining real-time records without manual input.
- Real-Time Collaboration: Multiple users can edit and view spreadsheets simultaneously from anywhere.
- Rich Data Manipulation: Support for formulas, charts, pivots, and add-ons to analyze and visualize data.
- Easy Data Import/Export: Ability to connect and sync data from various sources using integrations and APIs.
- Powerful Permissions: Fine-grained sharing, access controls, and version history for team management.
<iframe
width="100%"
height="400"
src="https://www.youtube.com/embed/cO86qTj7qeY"
title="Use the Google Sheets Write tool in Sim"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
Explore how to leverage the Google Sheets "Update" tool in Sim to modify existing entries in your spreadsheets based on workflow execution. This tutorial demonstrates setting up the update logic, mapping data fields, and synchronizing changes instantly. Perfect for keeping your data current and consistent.
<iframe
width="100%"
height="400"
src="https://www.youtube.com/embed/95by2fL9yn4"
title="Use the Google Sheets Update tool in Sim"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
Learn how to use the Google Sheets "Append" tool in Sim to effortlessly add new rows of data to your spreadsheets during workflow execution. This tutorial walks you through setting up the integration, configuring append actions, and ensuring smooth data growth. Perfect for expanding records without manual effort!
<iframe
width="100%"
height="400"
src="https://www.youtube.com/embed/8DgNvLBCsAo"
title="Use the Google Sheets Append tool in Sim"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
With Google Sheets, you can:
- **Create and edit spreadsheets**: Develop data-driven documents with comprehensive formatting and calculation options
- **Collaborate in real-time**: Work simultaneously with multiple users on the same spreadsheet
- **Analyze data**: Use formulas, functions, and pivot tables to process and understand your data
- **Visualize information**: Create charts, graphs, and conditional formatting to represent data visually
- **Access anywhere**: Use Google Sheets across devices with automatic cloud synchronization
- **Work offline**: Continue working without internet connection with changes syncing when back online
- **Integrate with other services**: Connect with Google Drive, Forms, and third-party applications
In Sim, the Google Sheets integration enables your agents to interact directly with spreadsheet data programmatically. This allows for powerful automation scenarios such as data extraction, analysis, reporting, and management. Your agents can read existing spreadsheets to extract information, write to spreadsheets to update data, and create new spreadsheets from scratch. This integration bridges the gap between your AI workflows and data management, enabling seamless interaction with structured data. By connecting Sim with Google Sheets, you can automate data workflows, generate reports, extract insights from data, and maintain up-to-date information - all through your intelligent agents. The integration supports various data formats and range specifications, making it flexible enough to handle diverse data management needs while maintaining the collaborative and accessible nature of Google Sheets.
In Sim, the Google Sheets integration empowers your agents to automate reading from, writing to, and updating specific sheets within spreadsheets. Agents can interact programmatically with Google Sheets to retrieve or modify data, manage collaborative documents, and automate reporting or record-keeping as part of your AI workflows. By connecting Sim with Google Sheets, you can build intelligent agents that manage, analyze, and update your data dynamically—streamlining operations, enhancing productivity, and ensuring up-to-date data access across your organization.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Google Sheets into the workflow. Can read, write, append, and update data.
Integrate Google Sheets into the workflow with explicit sheet selection. Can read, write, append, and update data in specific sheets.
@@ -85,34 +36,38 @@ Integrate Google Sheets into the workflow. Can read, write, append, and update d
### `google_sheets_read`
Read data from a Google Sheets spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet \(found in the URL: docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit\). |
| `range` | string | No | The A1 notation range to read \(e.g. "Sheet1!A1:D10", "A1:B5"\). Defaults to first sheet A1:Z1000 if not specified. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | json | Sheet data including range and cell values |
| `metadata` | json | Spreadsheet metadata including ID and URL |
| ↳ `spreadsheetId` | string | Google Sheets spreadsheet ID |
| ↳ `spreadsheetUrl` | string | Spreadsheet URL |
### `google_sheets_write`
Write data to a Google Sheets spreadsheet
Read data from a specific sheet in a Google Sheets spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet |
| `range` | string | No | The A1 notation range to write to \(e.g. "Sheet1!A1:D10", "A1:B5"\) |
| `sheetName` | string | Yes | The name of the sheet/tab to read from |
| `cellRange` | string | No | The cell range to read \(e.g. "A1:D10"\). Defaults to "A1:Z1000" if not specified. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sheetName` | string | Name of the sheet that was read |
| `range` | string | The range of cells that was read |
| `values` | array | The cell values as a 2D array |
| `metadata` | json | Spreadsheet metadata including ID and URL |
| ↳ `spreadsheetId` | string | Google Sheets spreadsheet ID |
| ↳ `spreadsheetUrl` | string | Spreadsheet URL |
### `google_sheets_write`
Write data to a specific sheet in a Google Sheets spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet |
| `sheetName` | string | Yes | The name of the sheet/tab to write to |
| `cellRange` | string | No | The cell range to write to \(e.g. "A1:D10", "A1"\). Defaults to "A1" if not specified. |
| `values` | array | Yes | The data to write as a 2D array \(e.g. \[\["Name", "Age"\], \["Alice", 30\], \["Bob", 25\]\]\) or array of objects. |
| `valueInputOption` | string | No | The format of the data to write |
| `includeValuesInResponse` | boolean | No | Whether to include the written values in the response |
@@ -131,14 +86,15 @@ Write data to a Google Sheets spreadsheet
### `google_sheets_update`
Update data in a Google Sheets spreadsheet
Update data in a specific sheet in a Google Sheets spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet to update |
| `range` | string | No | The A1 notation range to update \(e.g. "Sheet1!A1:D10", "A1:B5"\) |
| `sheetName` | string | Yes | The name of the sheet/tab to update |
| `cellRange` | string | No | The cell range to update \(e.g. "A1:D10", "A1"\). Defaults to "A1" if not specified. |
| `values` | array | Yes | The data to update as a 2D array \(e.g. \[\["Name", "Age"\], \["Alice", 30\]\]\) or array of objects. |
| `valueInputOption` | string | No | The format of the data to update |
| `includeValuesInResponse` | boolean | No | Whether to include the updated values in the response |
@@ -157,14 +113,14 @@ Update data in a Google Sheets spreadsheet
### `google_sheets_append`
Append data to the end of a Google Sheets spreadsheet
Append data to the end of a specific sheet in a Google Sheets spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet to append to |
| `range` | string | No | The A1 notation range to append after \(e.g. "Sheet1", "Sheet1!A:D"\) |
| `sheetName` | string | Yes | The name of the sheet/tab to append to |
| `values` | array | Yes | The data to append as a 2D array \(e.g. \[\["Alice", 30\], \["Bob", 25\]\]\) or array of objects. |
| `valueInputOption` | string | No | The format of the data to append |
| `insertDataOption` | string | No | How to insert the data \(OVERWRITE or INSERT_ROWS\) |

View File

@@ -10,6 +10,22 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#E8F0FE"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Vault](https://workspace.google.com/products/vault/) is a core information governance and eDiscovery tool for organizations using Google Workspace. With Google Vault, you can retain, search, and export users' Google Workspace data (such as Gmail, Drive, Groups, and Meet) to support litigation, regulatory compliance, and internal investigations.
Vault provides administrators and legal teams with powerful controls to manage the lifecycle of business communications. You can place data on legal hold, create matters to group eDiscovery activities, search message and file content across your organization, and export relevant data for review.
Key features of Google Vault include:
- **Search & Export**: Search across Gmail, Drive, Groups, and Meet for relevant data and export the results for analysis.
- **Matters & Holds**: Create matters (cases) and apply legal holds to retain user data beyond standard retention policies.
- **Retention Rules**: Define retention rules to keep or delete data after a set period to meet business and compliance needs.
- **Audit & Review**: Monitor Vault activity and enforce compliance with organization-wide policies.
In Sim, the Google Vault integration lets your AI agents programmatically manage Vault matters, exports, and holds. This enables automated workflows for legal discovery, compliance archiving, and data retention management in your organization. Agents can initiate new exports, list available holds, manage legal matters, and retrieve exported files directly through your automated processes, ensuring your compliance and information governance needs are met efficiently and securely.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Connect Google Vault to create exports, list exports, and manage holds within matters.
@@ -20,44 +36,43 @@ Connect Google Vault to create exports, list exports, and manage holds within ma
### `google_vault_create_matters_export`
Create an export in a matter
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `matterId` | string | Yes | No description |
| `exportName` | string | Yes | No description |
| `corpus` | string | Yes | Data corpus to export \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) |
| `accountEmails` | string | No | Comma-separated list of user emails to scope export |
| `orgUnitId` | string | No | Organization unit ID to scope export \(alternative to emails\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `export` | json | Created export object |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |
### `google_vault_list_matters_export`
List exports for a matter
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `matterId` | string | Yes | No description |
| `pageSize` | number | No | No description |
| `pageToken` | string | No | No description |
| `exportId` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `exports` | json | Array of export objects |
| `export` | json | Single export object \(when exportId is provided\) |
| `nextPageToken` | string | Token for fetching next page of results |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |
### `google_vault_download_export_file`
@@ -80,80 +95,82 @@ Download a single file from a Google Vault export (GCS object)
### `google_vault_create_matters_holds`
Create a hold in a matter
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `matterId` | string | Yes | No description |
| `holdName` | string | Yes | No description |
| `corpus` | string | Yes | Data corpus to hold \(MAIL, DRIVE, GROUPS, HANGOUTS_CHAT, VOICE\) |
| `accountEmails` | string | No | Comma-separated list of user emails to put on hold |
| `orgUnitId` | string | No | Organization unit ID to put on hold \(alternative to accounts\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hold` | json | Created hold object |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |
### `google_vault_list_matters_holds`
List holds for a matter
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `matterId` | string | Yes | No description |
| `pageSize` | number | No | No description |
| `pageToken` | string | No | No description |
| `holdId` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `holds` | json | Array of hold objects |
| `hold` | json | Single hold object \(when holdId is provided\) |
| `nextPageToken` | string | Token for fetching next page of results |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |
### `google_vault_create_matters`
Create a new matter in Google Vault
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | No description |
| `description` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `matter` | json | Created matter object |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |
### `google_vault_list_matters`
List matters, or get a specific matter if matterId is provided
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `pageSize` | number | No | No description |
| `pageToken` | string | No | No description |
| `matterId` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `matters` | json | Array of matter objects |
| `matter` | json | Single matter object \(when matterId is provided\) |
| `nextPageToken` | string | Token for fetching next page of results |
| `matters` | json | Array of matter objects \(for list_matters\) |
| `exports` | json | Array of export objects \(for list_matters_export\) |
| `holds` | json | Array of hold objects \(for list_matters_holds\) |
| `matter` | json | Created matter object \(for create_matters\) |
| `export` | json | Created export object \(for create_matters_export\) |
| `hold` | json | Created hold object \(for create_matters_holds\) |
| `file` | json | Downloaded export file \(UserFile\) from execution files |
| `nextPageToken` | string | Token for fetching next page of results \(for list operations\) |

View File

@@ -1,15 +1,36 @@
---
title: Intercom
description: Contact object with id, type, role, email, phone, name, external_id, created_at, updated_at
description: Manage contacts, companies, conversations, tickets, and messages in Intercom
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="intercom_v2"
color="#F5F5F5"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
Supercharge your customer communications and relationship management with [Intercom](https://www.intercom.com/) the all-in-one messaging platform for engaging, supporting, and retaining your customers. Integrate Intercom into your workflows to centralize conversations, contacts, support tickets, and more, all seamlessly accessible via automation.
With the Intercom tool, you can:
- **Create and manage contacts**: Easily add, update, search, list, and delete contacts to maintain a clean, actionable customer database.
- **Organize companies**: Create, get, and list companies to understand and support your customer organizations at scale.
- **Centralize customer conversations**: Retrieve, list, reply to, and search customer conversations to ensure no message slips through the cracks and support responses are always timely.
- **Manage tickets and messages**: Create and fetch tickets, as well as compose outbound messages, to deliver proactive, high-quality support experiences.
- **Automate and extend workflows**: Connect Intercom operations with your automations to trigger follow-ups, orchestrate customer journeys, and sync data with your stack.
Intercom empowers sales, support, and success teams to deliver personalized, efficient, and scalable customer experiences—whether you need to onboard new users, troubleshoot issues, or engage your customer base in real time.
Drive deeper relationships, faster response times, and smarter workflows by integrating Intercom with your automated processes today.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Intercom into the workflow. Can create, get, update, list, search, and delete contacts; create, get, and list companies; get, list, reply, and search conversations; create and get tickets; and create messages.
## Tools

View File

@@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Jira Service Management](https://www.atlassian.com/software/jira/service-management) is Atlassians modern IT service management (ITSM) solution designed to help teams efficiently manage service requests, incidents, problems, assets, and changes across your organization. Built on the Jira platform, Jira Service Management empowers IT, DevOps, HR, facilities, and other business teams to deliver exceptional, collaborative service.
Jira Service Management (JSM) goes beyond traditional issue tracking by providing features purpose-built for service teams, such as integrated SLAs, customizable request types, automation, robust queues, and seamless customer portals. Through the JSM API and Sim integration, you can automate, monitor, and interact with all aspects of your service management workflows.
Key features of Jira Service Management include:
- **Service Request Management**: Streamline intake, triage, and resolution of IT or business requests via customizable forms, queues, and automated routing.
- **Incident and Problem Management**: Log, track, escalate, and resolve incidents with tools for root cause analysis, post-incident reviews, and real-time collaboration.
- **SLA Tracking and Reporting**: Define, monitor, and report on service level agreements to ensure timely service delivery and accountability.
- **Asset and Configuration Management**: Maintain an up-to-date inventory of assets, configuration items, and their relationships to improve visibility and impact analysis.
- **Queue Management**: Manage workload and priorities using powerful queues for service agents, including filtering, sorting, and bulk actions.
- **Customer and Organization Management**: Group customers into organizations, manage user permissions, and improve communication through tailored customer portals.
With Sims Jira Service Management integration, you can create, monitor, and update service requests, fetch and manage customer organizations, track active queues, and automate ITSM operations programmatically. Build intelligent service desk agents, automate workflows triggered by service events, and ensure both end-users and service teams benefit from proactive, AI-powered support.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate with Jira Service Management for IT service management. Create and manage service requests, handle customers and organizations, track SLAs, and manage queues.

View File

@@ -10,6 +10,23 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#181C1E"
/>
{/* MANUAL-CONTENT-START:intro */}
Unlock deep visibility and understanding of your AI workflows with [LangSmith](https://www.langchain.com/langsmith) a powerful platform for tracing, debugging, and monitoring LLM-powered applications and automations. Integrate LangSmith into your processes to capture detailed execution traces, log input/output data, attach metadata, and optimize your workflows through data-driven observability.
With the LangSmith integration, you can:
- **Trace and debug runs**: Forward workflow, tool, or model runs to LangSmith, record hierarchical execution details, and pinpoint bottlenecks or failures quickly.
- **Attach rich metadata**: Enrich your traces by logging inputs, outputs, tags, custom metadata, reasons for failure, and more for in-depth insight and analytics.
- **Monitor workflow performance**: Visualize executions, monitor error rates, durations, and success metrics over time to improve reliability and efficiency.
- **Collaborate and audit**: Enable team-based debugging and track changes, enabling transparent auditing and rapid iteration on chained LLM workflows.
- **Automate observability**: Seamlessly connect LangSmith traces to your workflow automations for always-on, effortless monitoring without manual instrumentation.
LangSmith empowers engineers, data scientists, and product teams to iterate faster, catch issues earlier, and build more robust LLM-based applications—whether youre orchestrating agents, chains, or end-to-end workflows.
Drive better observability, actionable insights, and higher product quality by integrating LangSmith into your automated processes today.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Send run data to LangSmith to trace executions, attach metadata, and monitor workflow performance.

View File

@@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#316BFF"
/>
{/* MANUAL-CONTENT-START:intro */}
Supercharge your sales outreach and engagement with [Lemlist](https://lemlist.com) the personalized outreach automation platform trusted by thousands of sales teams. With Lemlist, you can automate multi-channel campaigns, nurture leads, and boost reply rates, all while keeping your communication highly personalized and authentic.
With the Lemlist integration, you can:
- **Automate outreach sequences:** Launch personalized email, LinkedIn, and calling campaigns at scale, tailored to each recipient.
- **Track campaign activity:** Instantly monitor opens, clicks, replies, bounces, and every lead interaction for granular campaign insights.
- **Centralize engagement data:** Fetch real-time activity and replies for each campaign or lead, and sync it directly into your workflow automation.
- **Get lead details automatically:** Retrieve enriched lead information by email or ID to keep your CRM and processes up to date without manual data entry.
- **Send targeted emails from your inbox:** Trigger bespoke emails to leads directly from the workflow, using up-to-date templates and data.
- **Boost team collaboration and follow-up:** Assign leads, track outcomes, and ensure no prospect is lost thanks to Lemlists built-in tools—all accessible via automation.
Lemlist empowers sales, marketing, and outbound teams to save time, personalize at scale, and convert more prospects. Automate and optimize your campaigns, integrate with your stack, and never miss a valuable opportunity.
Drive more replies, book more meetings, and grow your pipeline by connecting Lemlist to your automated workflows today!
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Lemlist into your workflow. Retrieve campaign activities and replies, get lead information, and send emails through the Lemlist inbox.

View File

@@ -1,33 +1,36 @@
---
title: Microsoft Excel
description: Read, write, and update data
description: Read and write data with sheet selection
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="microsoft_excel"
type="microsoft_excel_v2"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Microsoft Teams](https://www.microsoft.com/en-us/microsoft-365/excel) is a powerful spreadsheet application that enables data management, analysis, and visualization. Through the Microsoft Excel integration in Sim, you can programmatically read, write, and manipulate spreadsheet data to support your workflow automation needs.
[Microsoft Excel](https://www.microsoft.com/en-us/microsoft-365/excel) is the worlds most popular spreadsheet tool for organizing, analyzing, and visualizing data. The Microsoft Excel integration enables you to programmatically read from and write to Excel workbooks as part of your automation workflows, with easy selection of sheets and cell ranges.
With Microsoft Excel integration, you can:
- **Read Spreadsheet Data**: Access data from specific ranges, sheets, and cells
- **Write and Update Data**: Add new data or modify existing spreadsheet content
- **Manage Tables**: Create and manipulate tabular data structures
- **Handle Multiple Sheets**: Work with multiple worksheets in a workbook
- **Process Data**: Import, export, and transform spreadsheet data
- **Read data from specific sheets:** Extract table data, lists, records, or custom ranges from any sheet in your Excel files for use in downstream workflow steps.
- **Write and update data:** Programmatically update cells, insert new rows, or modify existing records in chosen sheets, without ever opening the file manually.
- **Select cell ranges:** Specify exact ranges (e.g., A1:D10) to target for read and write operations, allowing fine-grained data manipulation.
- **Automate calculations and reporting:** Pull in live data, perform calculations, and produce custom Excel reports automatically.
- **Centralize spreadsheet tasks:** Remove manual copy-pasting—the workflow can update Excel files, capture new submissions, or sync data between systems.
- **Integrate seamlessly with Microsoft 365:** Access and modify cloud-hosted spreadsheets, whether for finance, sales, operations, or analytics.
In Sim, the Microsoft Excel integration provides seamless access to spreadsheet functionality through OAuth authentication. You can read data from specific ranges, write new information, update existing cells, and handle various data formats. The integration supports both reading and writing operations with flexible input and output options. This enables you to build workflows that can effectively manage spreadsheet data, whether you're extracting information for analysis, updating records automatically, or maintaining data consistency across your applications.
Excels integration empowers every type of user—from analysts to managers—to automate their data collection, enrich reporting, trigger actions from sheet updates, and keep important data always up-to-date.
Connect Microsoft Excel to your automations to streamline data management, reporting, and collaboration within your workflows.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Microsoft Excel into the workflow. Can read, write, update, add to table, and create new worksheets.
Integrate Microsoft Excel into the workflow with explicit sheet selection. Can read and write data in specific sheets.
@@ -35,37 +38,39 @@ Integrate Microsoft Excel into the workflow. Can read, write, update, add to tab
### `microsoft_excel_read`
Read data from a Microsoft Excel spreadsheet
Read data from a specific sheet in a Microsoft Excel spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet to read from |
| `range` | string | No | The range of cells to read from. Accepts "SheetName!A1:B2" for explicit ranges or just "SheetName" to read the used range of that sheet. If omitted, reads the used range of the first sheet. |
| `sheetName` | string | Yes | The name of the sheet/tab to read from |
| `cellRange` | string | No | The cell range to read \(e.g., "A1:D10"\). If not specified, reads the entire used range. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `data` | object | Range data from the spreadsheet |
| `range` | string | The range that was read |
| `values` | array | Array of rows containing cell values |
| `metadata` | object | Spreadsheet metadata |
| ↳ `spreadsheetId` | string | The ID of the spreadsheet |
| ↳ `spreadsheetUrl` | string | URL to access the spreadsheet |
| `sheetName` | string | Name of the sheet that was read |
| `range` | string | The range that was read |
| `values` | array | Array of rows containing cell values |
| `metadata` | json | Spreadsheet metadata including ID and URL |
| ↳ `spreadsheetId` | string | Microsoft Excel spreadsheet ID |
| ↳ `spreadsheetUrl` | string | Spreadsheet URL |
### `microsoft_excel_write`
Write data to a Microsoft Excel spreadsheet
Write data to a specific sheet in a Microsoft Excel spreadsheet
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet to write to |
| `range` | string | No | The range of cells to write to |
| `values` | array | Yes | The data to write to the spreadsheet |
| `sheetName` | string | Yes | The name of the sheet/tab to write to |
| `cellRange` | string | No | The cell range to write to \(e.g., "A1:D10", "A1"\). Defaults to "A1" if not specified. |
| `values` | array | Yes | The data to write as a 2D array \(e.g. \[\["Name", "Age"\], \["Alice", 30\], \["Bob", 25\]\]\) or array of objects. |
| `valueInputOption` | string | No | The format of the data to write |
| `includeValuesInResponse` | boolean | No | Whether to include the written values in the response |
@@ -73,58 +78,12 @@ Write data to a Microsoft Excel spreadsheet
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `updatedRange` | string | The range that was updated |
| `updatedRows` | number | Number of rows that were updated |
| `updatedColumns` | number | Number of columns that were updated |
| `updatedCells` | number | Number of cells that were updated |
| `metadata` | object | Spreadsheet metadata |
| ↳ `spreadsheetId` | string | The ID of the spreadsheet |
| ↳ `spreadsheetUrl` | string | URL to access the spreadsheet |
### `microsoft_excel_table_add`
Add new rows to a Microsoft Excel table
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the spreadsheet containing the table |
| `tableName` | string | Yes | The name of the table to add rows to |
| `values` | array | Yes | The data to add to the table \(array of arrays or array of objects\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `index` | number | Index of the first row that was added |
| `values` | array | Array of rows that were added to the table |
| `metadata` | object | Spreadsheet metadata |
| ↳ `spreadsheetId` | string | The ID of the spreadsheet |
| ↳ `spreadsheetUrl` | string | URL to access the spreadsheet |
### `microsoft_excel_worksheet_add`
Create a new worksheet (sheet) in a Microsoft Excel workbook
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `spreadsheetId` | string | Yes | The ID of the Excel workbook to add the worksheet to |
| `worksheetName` | string | Yes | The name of the new worksheet. Must be unique within the workbook and cannot exceed 31 characters |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `worksheet` | object | Details of the newly created worksheet |
| ↳ `id` | string | The unique ID of the worksheet |
| ↳ `name` | string | The name of the worksheet |
| ↳ `position` | number | The zero-based position of the worksheet |
| ↳ `visibility` | string | The visibility state of the worksheet \(Visible/Hidden/VeryHidden\) |
| `metadata` | object | Spreadsheet metadata |
| ↳ `spreadsheetId` | string | The ID of the spreadsheet |
| ↳ `spreadsheetUrl` | string | URL to access the spreadsheet |
| `updatedRange` | string | Range of cells that were updated |
| `updatedRows` | number | Number of rows updated |
| `updatedColumns` | number | Number of columns updated |
| `updatedCells` | number | Number of cells updated |
| `metadata` | json | Spreadsheet metadata including ID and URL |
| ↳ `spreadsheetId` | string | Microsoft Excel spreadsheet ID |
| ↳ `spreadsheetUrl` | string | Spreadsheet URL |

View File

@@ -10,6 +10,22 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#FFFFFF"
/>
{/* MANUAL-CONTENT-START:intro */}
Easily manage, analyze, and visualize connected data with [Neo4j](https://neo4j.com) the industry-leading native graph database built for handling complex relationships at scale. Neo4j allows you to efficiently store, query, and explore data models that go beyond tables, making it ideal for real-time recommendations, fraud detection, knowledge graphs, and more.
With Neo4j integrations, you can:
- **Query complex relationships:** Use Cypher, Neo4js declarative graph query language, to easily find patterns, shortest paths, and recommendations within your graph data.
- **Create and update nodes and relationships:** Seamlessly add, update, and delete both nodes and relationships to keep your graph database always up to date.
- **Analyze graph structures:** Instantly analyze interconnected information, uncover hidden connections, and gain actionable insights not possible with traditional databases.
- **Centralize graph data in your workflows:** Connect Neo4j to your automation, enabling data enrichment and advanced analytics directly in your workflow.
- **Visualize and export results:** Retrieve query results for display in dashboards or export enriched data to other systems.
- **Scale with confidence:** Neo4j is trusted by enterprises worldwide for mission-critical applications, ensuring performance and reliability.
Whether building recommendation systems, anti-fraud solutions, knowledge graphs, or AI-powered applications, Neo4j empowers teams to unlock the full value of their connected data. Start integrating Neo4j into your workflows to make smarter decisions, faster.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Neo4j graph database into the workflow. Can query, create, merge, update, and delete nodes and relationships.

View File

@@ -10,6 +10,22 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#000000"
/>
{/* MANUAL-CONTENT-START:intro */}
Effortlessly discover, play, and organize music with **Spotify**—the ultimate streaming platform millions rely on for their music, podcasts, and audiobooks. Integrate Spotify into your workflows to control your musical experience and manage your library with ease.
With the Spotify integration, you can:
- **Search & explore:** Instantly search the Spotify catalog for tracks, albums, artists, or playlists to find exactly what youre looking for.
- **Manage playlists:** Create and modify playlists, add or remove tracks, and organize your favorite music collections on the go.
- **Control playback:** Play, pause, skip tracks, adjust volume, and switch playback devices seamlessly within your workflow.
- **Access your library:** Retrieve and browse your saved tracks, liked albums, and followed artists at any time.
- **Fetch detailed track info:** Get in-depth details on any track, album, or artist, including popularity, duration, and preview audio.
- **Seamlessly integrate music into automation:** Trigger specific music actions or updates based on events or other app workflows.
Spotify empowers music lovers, curators, and creators to personalize their audio experience, automate routine tasks, and spend more time enjoying and sharing what they love. Enhance your productivity and add a soundtrack to your day—connect Spotify to your automated workflows now!
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Spotify into your workflow. Search for tracks, albums, artists, and playlists. Manage playlists, access your library, control playback, browse podcasts and audiobooks.

View File

@@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#2EF598"
/>
{/* MANUAL-CONTENT-START:intro */}
Supercharge your real-time data pipelines and analytics with [Tinybird](https://tinybird.co) the fast, scalable platform for ingesting, querying, and building APIs on large volumes of event data. Tinybird enables developers and data engineers to collect, transform, and expose data instantly, making it easy to power dashboards, applications, and automation with fresh insights.
With the Tinybird integration, you can:
- **Stream events at scale:** Ingest millions of JSON events per second reliably, using HTTP-based APIs with NDJSON or JSON.
- **Query data with low latency:** Run complex SQL-based analytics and aggregation queries in real time, ideal for dashboards, alerting, and reports.
- **Expose data via instant APIs:** Build and publish API endpoints for your queries directly from the Tinybird UI or via their API.
- **Automate workflows:** Use Tinybirds APIs in your automations to fetch, transform, and sync data across your stack.
- **Monitor and debug:** Get insights into pipeline performance, query latencies, and ingestion health with real-time monitoring.
- **Secure access:** Leverage fine-grained authentication and resource scoping with personal or workspace API tokens.
Tinybird empowers engineering, analytics, and product teams to deliver lightning-fast, always-up-to-date data products with minimal operational overhead. Go from raw event data to production-ready endpoints in minutes.
Connect Tinybird to your workflows today to accelerate data-driven features, automation, and analytics!
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Interact with Tinybird using the Events API to stream JSON or NDJSON events, or use the Query API to execute SQL queries against Pipes and Data Sources.

View File

@@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#E8E8E8"
/>
{/* MANUAL-CONTENT-START:intro */}
Enhance your AI agents with reliable long-term memory using [Zep](https://getzep.com) the memory solution designed for LLM-based applications. Zep provides seamless storage, retrieval, and management of conversational context, allowing your agents to remember conversations and facts across sessions.
With the Zep integration, you can:
- **Store and retrieve chat history:** Maintain detailed records of entire conversations with robust APIs for adding and retrieving messages.
- **Summarize conversations:** Access AI-powered summaries of threads, helping agents recall key facts and context efficiently.
- **Extract and store structured facts:** Automatically pull out and manage important facts from conversations for persistent memory and reference.
- **Enrich agent responses:** Use stored knowledge to provide contextually aware interactions and decrease repetitive or irrelevant responses.
- **Centralize memory management:** Sync memory data directly with your workflows and ensure your agents always have access to critical user information.
- **Scale with privacy and control:** Manage memory for millions of users while maintaining control over data retention and access.
Zep empowers developers to build smarter, more contextual, and helpful AI applications. Focus on building great experiences—leave the memory to Zep.
Unlock richer conversations and more capable agents by integrating Zep into your automated workflows today!
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Zep for long-term memory management. Create threads, add messages, retrieve context with AI-powered summaries and facts extraction.

View File

@@ -0,0 +1,114 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('GoogleSheetsAPI')
interface SheetProperties {
sheetId: number
title: string
index: number
}
interface Sheet {
properties: SheetProperties
}
interface SpreadsheetResponse {
sheets: Sheet[]
}
/**
* Get sheets (tabs) from a Google Spreadsheet
*/
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] Google Sheets sheets request received`)
try {
const { searchParams } = new URL(request.url)
const credentialId = searchParams.get('credentialId')
const spreadsheetId = searchParams.get('spreadsheetId')
const workflowId = searchParams.get('workflowId') || undefined
if (!credentialId) {
logger.warn(`[${requestId}] Missing credentialId parameter`)
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
}
if (!spreadsheetId) {
logger.warn(`[${requestId}] Missing spreadsheetId parameter`)
return NextResponse.json({ error: 'Spreadsheet ID is required' }, { status: 400 })
}
const authz = await authorizeCredentialUse(request, { credentialId, workflowId })
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}
const accessToken = await refreshAccessTokenIfNeeded(
credentialId,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
}
logger.info(
`[${requestId}] Fetching sheets from Google Sheets API for spreadsheet ${spreadsheetId}`
)
// Fetch spreadsheet metadata to get sheet names
const sheetsResponse = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}?fields=sheets.properties`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
)
if (!sheetsResponse.ok) {
const errorData = await sheetsResponse
.text()
.then((text) => JSON.parse(text))
.catch(() => ({ error: { message: 'Unknown error' } }))
logger.error(`[${requestId}] Google Sheets API error`, {
status: sheetsResponse.status,
error: errorData.error?.message || 'Failed to fetch sheets',
})
return NextResponse.json(
{ error: errorData.error?.message || 'Failed to fetch sheets' },
{ status: sheetsResponse.status }
)
}
const data: SpreadsheetResponse = await sheetsResponse.json()
const sheets = data.sheets || []
// Sort sheets by index
sheets.sort((a, b) => a.properties.index - b.properties.index)
logger.info(`[${requestId}] Successfully fetched ${sheets.length} sheets`)
return NextResponse.json({
sheets: sheets.map((sheet) => ({
id: sheet.properties.title, // Use title as ID since that's what the API uses
name: sheet.properties.title,
sheetId: sheet.properties.sheetId,
index: sheet.properties.index,
})),
})
} catch (error) {
logger.error(`[${requestId}] Error fetching Google Sheets sheets`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View File

@@ -0,0 +1,112 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('MicrosoftExcelAPI')
interface Worksheet {
id: string
name: string
position: number
visibility: string
}
interface WorksheetsResponse {
value: Worksheet[]
}
/**
* Get worksheets (tabs) from a Microsoft Excel workbook
*/
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] Microsoft Excel sheets request received`)
try {
const { searchParams } = new URL(request.url)
const credentialId = searchParams.get('credentialId')
const spreadsheetId = searchParams.get('spreadsheetId')
const workflowId = searchParams.get('workflowId') || undefined
if (!credentialId) {
logger.warn(`[${requestId}] Missing credentialId parameter`)
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
}
if (!spreadsheetId) {
logger.warn(`[${requestId}] Missing spreadsheetId parameter`)
return NextResponse.json({ error: 'Spreadsheet ID is required' }, { status: 400 })
}
const authz = await authorizeCredentialUse(request, { credentialId, workflowId })
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}
const accessToken = await refreshAccessTokenIfNeeded(
credentialId,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
}
logger.info(
`[${requestId}] Fetching worksheets from Microsoft Graph API for workbook ${spreadsheetId}`
)
// Fetch worksheets from Microsoft Graph API
const worksheetsResponse = await fetch(
`https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}/workbook/worksheets`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
)
if (!worksheetsResponse.ok) {
const errorData = await worksheetsResponse
.text()
.then((text) => JSON.parse(text))
.catch(() => ({ error: { message: 'Unknown error' } }))
logger.error(`[${requestId}] Microsoft Graph API error`, {
status: worksheetsResponse.status,
error: errorData.error?.message || 'Failed to fetch worksheets',
})
return NextResponse.json(
{ error: errorData.error?.message || 'Failed to fetch worksheets' },
{ status: worksheetsResponse.status }
)
}
const data: WorksheetsResponse = await worksheetsResponse.json()
const worksheets = data.value || []
// Sort worksheets by position
worksheets.sort((a, b) => a.position - b.position)
logger.info(`[${requestId}] Successfully fetched ${worksheets.length} worksheets`)
return NextResponse.json({
sheets: worksheets.map((worksheet) => ({
id: worksheet.name, // Use name as ID since that's what the API uses for addressing
name: worksheet.name,
worksheetId: worksheet.id,
position: worksheet.position,
visibility: worksheet.visibility,
})),
})
} catch (error) {
logger.error(`[${requestId}] Error fetching Microsoft Excel worksheets`, error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View File

@@ -22,6 +22,7 @@ export { MessagesInput } from './messages-input/messages-input'
export { ProjectSelectorInput } from './project-selector/project-selector-input'
export { ResponseFormat } from './response/response-format'
export { ScheduleInfo } from './schedule-info/schedule-info'
export { SheetSelectorInput } from './sheet-selector/sheet-selector-input'
export { ShortInput } from './short-input/short-input'
export { SlackSelectorInput } from './slack-selector/slack-selector-input'
export { SliderInput } from './slider-input/slider-input'

View File

@@ -0,0 +1,121 @@
'use client'
import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import type { SubBlockConfig } from '@/blocks/types'
import { resolveSelectorForSubBlock, type SelectorResolution } from '@/hooks/selectors/resolution'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
interface SheetSelectorInputProps {
blockId: string
subBlock: SubBlockConfig
disabled: boolean
isPreview?: boolean
previewValue?: any | null
previewContextValues?: Record<string, any>
}
export function SheetSelectorInput({
blockId,
subBlock,
disabled,
isPreview = false,
previewValue,
previewContextValues,
}: SheetSelectorInputProps) {
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
const { activeWorkflowId } = useWorkflowRegistry()
const params = useParams()
const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || ''
const { finalDisabled } = useDependsOnGate(blockId, subBlock, {
disabled,
isPreview,
previewContextValues,
})
const [connectedCredentialFromStore] = useSubBlockValue(blockId, 'credential')
const [spreadsheetIdFromStore] = useSubBlockValue(blockId, 'spreadsheetId')
const [manualSpreadsheetIdFromStore] = useSubBlockValue(blockId, 'manualSpreadsheetId')
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const spreadsheetId =
previewContextValues?.spreadsheetId ??
spreadsheetIdFromStore ??
previewContextValues?.manualSpreadsheetId ??
manualSpreadsheetIdFromStore
const normalizedCredentialId =
typeof connectedCredential === 'string'
? connectedCredential
: typeof connectedCredential === 'object' && connectedCredential !== null
? ((connectedCredential as Record<string, any>).id ?? '')
: ''
const normalizedSpreadsheetId = typeof spreadsheetId === 'string' ? spreadsheetId.trim() : ''
// Derive provider from serviceId using OAuth config
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const { isForeignCredential } = useForeignCredential(effectiveProviderId, normalizedCredentialId)
const selectorResolution = useMemo<SelectorResolution | null>(() => {
return resolveSelectorForSubBlock(subBlock, {
workflowId: workflowIdFromUrl,
credentialId: normalizedCredentialId,
spreadsheetId: normalizedSpreadsheetId,
})
}, [subBlock, workflowIdFromUrl, normalizedCredentialId, normalizedSpreadsheetId])
const missingCredential = !normalizedCredentialId
const missingSpreadsheet = !normalizedSpreadsheetId
const disabledReason =
finalDisabled ||
isForeignCredential ||
missingCredential ||
missingSpreadsheet ||
!selectorResolution?.key
if (!selectorResolution?.key) {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<div className='w-full rounded border p-4 text-center text-muted-foreground text-sm'>
Sheet selector not supported for service: {serviceId || 'unknown'}
</div>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<p>This sheet selector is not implemented for {serviceId || 'unknown'}</p>
</Tooltip.Content>
</Tooltip.Root>
)
}
return (
<SelectorCombobox
blockId={blockId}
subBlock={subBlock}
selectorKey={selectorResolution.key}
selectorContext={selectorResolution.context}
disabled={disabledReason}
isPreview={isPreview}
previewValue={previewValue ?? null}
placeholder={subBlock.placeholder || 'Select sheet'}
allowSearch={selectorResolution.allowSearch}
onOptionChange={(value) => {
if (!isPreview) {
collaborativeSetSubblockValue(blockId, subBlock.id, value)
}
}}
/>
)
}

View File

@@ -38,6 +38,7 @@ import {
FileUpload,
LongInput,
ProjectSelectorInput,
SheetSelectorInput,
ShortInput,
SlackSelectorInput,
SliderInput,
@@ -263,6 +264,43 @@ function FileSelectorSyncWrapper({
)
}
function SheetSelectorSyncWrapper({
blockId,
paramId,
value,
onChange,
uiComponent,
disabled,
previewContextValues,
}: {
blockId: string
paramId: string
value: string
onChange: (value: string) => void
uiComponent: any
disabled: boolean
previewContextValues?: Record<string, any>
}) {
return (
<GenericSyncWrapper blockId={blockId} paramId={paramId} value={value} onChange={onChange}>
<SheetSelectorInput
blockId={blockId}
subBlock={{
id: paramId,
type: 'sheet-selector' as const,
title: paramId,
serviceId: uiComponent.serviceId,
requiredScopes: uiComponent.requiredScopes || [],
placeholder: uiComponent.placeholder,
dependsOn: uiComponent.dependsOn,
}}
disabled={disabled}
previewContextValues={previewContextValues}
/>
</GenericSyncWrapper>
)
}
function KnowledgeBaseSelectorSyncWrapper({
blockId,
paramId,
@@ -2026,6 +2064,19 @@ export function ToolInput({
/>
)
case 'sheet-selector':
return (
<SheetSelectorSyncWrapper
blockId={blockId}
paramId={param.id}
value={value}
onChange={onChange}
uiComponent={uiComponent}
disabled={disabled}
previewContextValues={currentToolParams as any}
/>
)
case 'table':
return (
<TableSyncWrapper

View File

@@ -30,6 +30,7 @@ import {
ProjectSelectorInput,
ResponseFormat,
ScheduleInfo,
SheetSelectorInput,
ShortInput,
SlackSelectorInput,
SliderInput,
@@ -684,6 +685,18 @@ function SubBlockComponent({
/>
)
case 'sheet-selector':
return (
<SheetSelectorInput
blockId={blockId}
subBlock={config}
disabled={isDisabled}
isPreview={isPreview}
previewValue={previewValue}
previewContextValues={isPreview ? subBlockValues : undefined}
/>
)
case 'project-selector':
return (
<ProjectSelectorInput

View File

@@ -386,6 +386,7 @@ describe('Blocks Module', () => {
'webhook-config',
'schedule-info',
'file-selector',
'sheet-selector',
'project-selector',
'channel-selector',
'user-selector',

View File

@@ -176,6 +176,9 @@ export const CursorV2Block: BlockConfig<CursorResponse> = {
...CursorBlock,
type: 'cursor_v2',
name: 'Cursor',
description: 'Launch and manage Cursor cloud agents to work on GitHub repositories',
longDescription:
'Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.',
hideFromToolbar: false,
tools: {
...CursorBlock.tools,

View File

@@ -3,11 +3,13 @@ import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GoogleSheetsResponse } from '@/tools/google_sheets/types'
// Legacy block - hidden from toolbar
export const GoogleSheetsBlock: BlockConfig<GoogleSheetsResponse> = {
type: 'google_sheets',
name: 'Google Sheets',
name: 'Google Sheets (Legacy)',
description: 'Read, write, and update data',
authMode: AuthMode.OAuth,
hideFromToolbar: true,
longDescription:
'Integrate Google Sheets into the workflow. Can read, write, append, and update data.',
docsLink: 'https://docs.sim.ai/tools/google_sheets',

View File

@@ -0,0 +1,360 @@
import { GoogleSheetsIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GoogleSheetsV2Response } from '@/tools/google_sheets/types'
export const GoogleSheetsV2Block: BlockConfig<GoogleSheetsV2Response> = {
type: 'google_sheets_v2',
name: 'Google Sheets',
description: 'Read, write, and update data with sheet selection',
authMode: AuthMode.OAuth,
hideFromToolbar: false,
longDescription:
'Integrate Google Sheets into the workflow with explicit sheet selection. Can read, write, append, and update data in specific sheets.',
docsLink: 'https://docs.sim.ai/tools/google_sheets',
category: 'tools',
bgColor: '#E0E0E0',
icon: GoogleSheetsIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Read Data', id: 'read' },
{ label: 'Write Data', id: 'write' },
{ label: 'Update Data', id: 'update' },
{ label: 'Append Data', id: 'append' },
],
value: () => 'read',
},
// Google Sheets Credentials
{
id: 'credential',
title: 'Google Account',
type: 'oauth-input',
required: true,
serviceId: 'google-sheets',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive',
],
placeholder: 'Select Google account',
},
// Spreadsheet Selector (basic mode)
{
id: 'spreadsheetId',
title: 'Select Spreadsheet',
type: 'file-selector',
canonicalParamId: 'spreadsheetId',
serviceId: 'google-sheets',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive',
],
mimeType: 'application/vnd.google-apps.spreadsheet',
placeholder: 'Select a spreadsheet',
dependsOn: ['credential'],
mode: 'basic',
},
// Manual Spreadsheet ID (advanced mode)
{
id: 'manualSpreadsheetId',
title: 'Spreadsheet ID',
type: 'short-input',
canonicalParamId: 'spreadsheetId',
placeholder: 'ID of the spreadsheet (from URL)',
dependsOn: ['credential'],
mode: 'advanced',
},
// Sheet Name Selector (basic mode)
{
id: 'sheetName',
title: 'Sheet (Tab)',
type: 'sheet-selector',
canonicalParamId: 'sheetName',
serviceId: 'google-sheets',
placeholder: 'Select a sheet',
required: true,
dependsOn: { all: ['credential'], any: ['spreadsheetId', 'manualSpreadsheetId'] },
mode: 'basic',
},
// Manual Sheet Name (advanced mode)
{
id: 'manualSheetName',
title: 'Sheet Name',
type: 'short-input',
canonicalParamId: 'sheetName',
placeholder: 'Name of the sheet/tab (e.g., Sheet1)',
required: true,
dependsOn: ['credential'],
mode: 'advanced',
},
// Cell Range (optional for read/write/update)
{
id: 'cellRange',
title: 'Cell Range',
type: 'short-input',
placeholder: 'Cell range (e.g., A1:D10). Defaults to A1 for write.',
condition: { field: 'operation', value: ['read', 'write', 'update'] },
wandConfig: {
enabled: true,
prompt: `Generate a valid cell range based on the user's description.
### VALID FORMATS
- Single cell: A1
- Range: A1:D10
- Entire column: A:A
- Entire row: 1:1
- Multiple columns: A:D
- Multiple rows: 1:10
### RANGE RULES
- Column letters are uppercase: A, B, C, ... Z, AA, AB, etc.
- Row numbers start at 1 (not 0)
### EXAMPLES
- "first 100 rows" -> A1:Z100
- "cells A1 through C50" -> A1:C50
- "column A" -> A:A
- "just the headers row" -> 1:1
- "first cell" -> A1
Return ONLY the range string - no sheet name, no explanations, no quotes.`,
placeholder: 'Describe the range (e.g., "first 50 rows" or "column A")...',
},
},
// Write-specific Fields
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1", "B1"], ["A2", "B2"]]) or an array of objects (e.g., [{"name":"John", "age":30}])',
condition: { field: 'operation', value: 'write' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate Google Sheets data as a JSON array based on the user's description.
Format options:
1. Array of arrays: [["Header1", "Header2"], ["Value1", "Value2"]]
2. Array of objects: [{"column1": "value1", "column2": "value2"}]
Examples:
- "sales data with product and revenue columns" -> [["Product", "Revenue"], ["Widget A", 1500], ["Widget B", 2300]]
- "list of employees with name and email" -> [{"name": "John Doe", "email": "john@example.com"}, {"name": "Jane Smith", "email": "jane@example.com"}]
Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the data you want to write...',
generationType: 'json-object',
},
},
{
id: 'valueInputOption',
title: 'Value Input Option',
type: 'dropdown',
options: [
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
],
condition: { field: 'operation', value: 'write' },
},
// Update-specific Fields
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1", "B1"], ["A2", "B2"]]) or an array of objects',
condition: { field: 'operation', value: 'update' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate Google Sheets data as a JSON array based on the user's description.
Format options:
1. Array of arrays: [["Header1", "Header2"], ["Value1", "Value2"]]
2. Array of objects: [{"column1": "value1", "column2": "value2"}]
Examples:
- "update with new prices" -> [["Product", "Price"], ["Widget A", 29.99], ["Widget B", 49.99]]
- "quarterly targets" -> [{"Q1": 10000, "Q2": 12000, "Q3": 15000, "Q4": 18000}]
Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the data you want to update...',
generationType: 'json-object',
},
},
{
id: 'valueInputOption',
title: 'Value Input Option',
type: 'dropdown',
options: [
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
],
condition: { field: 'operation', value: 'update' },
},
// Append-specific Fields
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1", "B1"], ["A2", "B2"]]) or an array of objects',
condition: { field: 'operation', value: 'append' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate Google Sheets data as a JSON array based on the user's description.
Format options:
1. Array of arrays: [["Value1", "Value2"], ["Value3", "Value4"]]
2. Array of objects: [{"column1": "value1", "column2": "value2"}]
Examples:
- "add new sales record" -> [["2024-01-15", "Widget Pro", 5, 249.99]]
- "append customer info" -> [{"name": "Acme Corp", "contact": "John Smith", "status": "Active"}]
Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the data you want to append...',
generationType: 'json-object',
},
},
{
id: 'valueInputOption',
title: 'Value Input Option',
type: 'dropdown',
options: [
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
],
condition: { field: 'operation', value: 'append' },
},
{
id: 'insertDataOption',
title: 'Insert Data Option',
type: 'dropdown',
options: [
{ label: 'Insert Rows (Add new rows)', id: 'INSERT_ROWS' },
{ label: 'Overwrite (Add to existing data)', id: 'OVERWRITE' },
],
condition: { field: 'operation', value: 'append' },
},
],
tools: {
access: [
'google_sheets_read_v2',
'google_sheets_write_v2',
'google_sheets_update_v2',
'google_sheets_append_v2',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'read':
return 'google_sheets_read_v2'
case 'write':
return 'google_sheets_write_v2'
case 'update':
return 'google_sheets_update_v2'
case 'append':
return 'google_sheets_append_v2'
default:
throw new Error(`Invalid Google Sheets V2 operation: ${params.operation}`)
}
},
params: (params) => {
const {
credential,
values,
spreadsheetId,
manualSpreadsheetId,
sheetName,
manualSheetName,
cellRange,
...rest
} = params
const parsedValues = values ? JSON.parse(values as string) : undefined
const effectiveSpreadsheetId = (spreadsheetId || manualSpreadsheetId || '').trim()
const effectiveSheetName = ((sheetName || manualSheetName || '') as string).trim()
if (!effectiveSpreadsheetId) {
throw new Error('Spreadsheet ID is required.')
}
if (!effectiveSheetName) {
throw new Error('Sheet name is required. Please select or enter a sheet name.')
}
return {
...rest,
spreadsheetId: effectiveSpreadsheetId,
sheetName: effectiveSheetName,
cellRange: cellRange ? (cellRange as string).trim() : undefined,
values: parsedValues,
credential,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Google Sheets access token' },
spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' },
manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' },
sheetName: { type: 'string', description: 'Name of the sheet/tab' },
manualSheetName: { type: 'string', description: 'Manual sheet name entry' },
cellRange: { type: 'string', description: 'Cell range (e.g., A1:D10)' },
values: { type: 'string', description: 'Cell values data' },
valueInputOption: { type: 'string', description: 'Value input option' },
insertDataOption: { type: 'string', description: 'Data insertion option' },
},
outputs: {
sheetName: {
type: 'string',
description: 'Name of the sheet',
condition: { field: 'operation', value: 'read' },
},
range: {
type: 'string',
description: 'Range that was read',
condition: { field: 'operation', value: 'read' },
},
values: {
type: 'json',
description: 'Cell values as 2D array',
condition: { field: 'operation', value: 'read' },
},
updatedRange: {
type: 'string',
description: 'Updated range',
condition: { field: 'operation', value: ['write', 'update', 'append'] },
},
updatedRows: {
type: 'number',
description: 'Updated rows count',
condition: { field: 'operation', value: ['write', 'update', 'append'] },
},
updatedColumns: {
type: 'number',
description: 'Updated columns count',
condition: { field: 'operation', value: ['write', 'update', 'append'] },
},
updatedCells: {
type: 'number',
description: 'Updated cells count',
condition: { field: 'operation', value: ['write', 'update', 'append'] },
},
tableRange: {
type: 'string',
description: 'Table range',
condition: { field: 'operation', value: 'append' },
},
metadata: { type: 'json', description: 'Spreadsheet metadata including ID and URL' },
},
}

View File

@@ -5,9 +5,10 @@ import type { MicrosoftExcelResponse } from '@/tools/microsoft_excel/types'
export const MicrosoftExcelBlock: BlockConfig<MicrosoftExcelResponse> = {
type: 'microsoft_excel',
name: 'Microsoft Excel',
name: 'Microsoft Excel (Legacy)',
description: 'Read, write, and update data',
authMode: AuthMode.OAuth,
hideFromToolbar: true,
longDescription:
'Integrate Microsoft Excel into the workflow. Can read, write, update, add to table, and create new worksheets.',
docsLink: 'https://docs.sim.ai/tools/microsoft_excel',

View File

@@ -0,0 +1,261 @@
import { MicrosoftExcelIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { MicrosoftExcelV2Response } from '@/tools/microsoft_excel/types'
export const MicrosoftExcelV2Block: BlockConfig<MicrosoftExcelV2Response> = {
type: 'microsoft_excel_v2',
name: 'Microsoft Excel',
description: 'Read and write data with sheet selection',
authMode: AuthMode.OAuth,
hideFromToolbar: false,
longDescription:
'Integrate Microsoft Excel into the workflow with explicit sheet selection. Can read and write data in specific sheets.',
docsLink: 'https://docs.sim.ai/tools/microsoft_excel',
category: 'tools',
bgColor: '#E0E0E0',
icon: MicrosoftExcelIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Read Data', id: 'read' },
{ label: 'Write Data', id: 'write' },
],
value: () => 'read',
},
// Microsoft Excel Credentials
{
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
serviceId: 'microsoft-excel',
requiredScopes: [
'openid',
'profile',
'email',
'Files.Read',
'Files.ReadWrite',
'offline_access',
],
placeholder: 'Select Microsoft account',
required: true,
},
// Spreadsheet Selector (basic mode)
{
id: 'spreadsheetId',
title: 'Select Spreadsheet',
type: 'file-selector',
canonicalParamId: 'spreadsheetId',
serviceId: 'microsoft-excel',
requiredScopes: [],
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
placeholder: 'Select a spreadsheet',
dependsOn: ['credential'],
mode: 'basic',
},
// Manual Spreadsheet ID (advanced mode)
{
id: 'manualSpreadsheetId',
title: 'Spreadsheet ID',
type: 'short-input',
canonicalParamId: 'spreadsheetId',
placeholder: 'Enter spreadsheet ID',
dependsOn: ['credential'],
mode: 'advanced',
},
// Sheet Name Selector (basic mode)
{
id: 'sheetName',
title: 'Sheet (Tab)',
type: 'sheet-selector',
canonicalParamId: 'sheetName',
serviceId: 'microsoft-excel',
placeholder: 'Select a sheet',
required: true,
dependsOn: { all: ['credential'], any: ['spreadsheetId', 'manualSpreadsheetId'] },
mode: 'basic',
},
// Manual Sheet Name (advanced mode)
{
id: 'manualSheetName',
title: 'Sheet Name',
type: 'short-input',
canonicalParamId: 'sheetName',
placeholder: 'Name of the sheet/tab (e.g., Sheet1)',
required: true,
dependsOn: ['credential'],
mode: 'advanced',
},
// Cell Range (optional for read/write)
{
id: 'cellRange',
title: 'Cell Range',
type: 'short-input',
placeholder: 'Cell range (e.g., A1:D10). Defaults to used range for read, A1 for write.',
wandConfig: {
enabled: true,
prompt: `Generate a valid cell range based on the user's description.
### VALID FORMATS
- Single cell: A1
- Range: A1:D10
- Entire column: A:A
- Entire row: 1:1
- Multiple columns: A:D
- Multiple rows: 1:10
### RANGE RULES
- Column letters are uppercase: A, B, C, ... Z, AA, AB, etc.
- Row numbers start at 1 (not 0)
### EXAMPLES
- "first 100 rows" -> A1:Z100
- "cells A1 through C50" -> A1:C50
- "column A" -> A:A
- "just the headers row" -> 1:1
- "first cell" -> A1
Return ONLY the range string - no sheet name, no explanations, no quotes.`,
placeholder: 'Describe the range (e.g., "first 50 rows" or "column A")...',
},
},
// Write-specific Fields
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1", "B1"], ["A2", "B2"]]) or an array of objects (e.g., [{"name":"John", "age":30}])',
condition: { field: 'operation', value: 'write' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate Microsoft Excel data as a JSON array based on the user's description.
Format options:
1. Array of arrays: [["Header1", "Header2"], ["Value1", "Value2"]]
2. Array of objects: [{"column1": "value1", "column2": "value2"}]
Examples:
- "sales data with product and revenue columns" -> [["Product", "Revenue"], ["Widget A", 1500], ["Widget B", 2300]]
- "list of employees with name and email" -> [{"name": "John Doe", "email": "john@example.com"}, {"name": "Jane Smith", "email": "jane@example.com"}]
Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the data you want to write...',
generationType: 'json-object',
},
},
{
id: 'valueInputOption',
title: 'Value Input Option',
type: 'dropdown',
options: [
{ label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' },
{ label: "Raw (Don't parse formulas)", id: 'RAW' },
],
condition: { field: 'operation', value: 'write' },
},
],
tools: {
access: ['microsoft_excel_read_v2', 'microsoft_excel_write_v2'],
config: {
tool: (params) => {
switch (params.operation) {
case 'read':
return 'microsoft_excel_read_v2'
case 'write':
return 'microsoft_excel_write_v2'
default:
throw new Error(`Invalid Microsoft Excel V2 operation: ${params.operation}`)
}
},
params: (params) => {
const {
credential,
values,
spreadsheetId,
manualSpreadsheetId,
sheetName,
manualSheetName,
cellRange,
...rest
} = params
const parsedValues = values ? JSON.parse(values as string) : undefined
const effectiveSpreadsheetId = (spreadsheetId || manualSpreadsheetId || '').trim()
const effectiveSheetName = ((sheetName || manualSheetName || '') as string).trim()
if (!effectiveSpreadsheetId) {
throw new Error('Spreadsheet ID is required.')
}
if (!effectiveSheetName) {
throw new Error('Sheet name is required. Please select or enter a sheet name.')
}
return {
...rest,
spreadsheetId: effectiveSpreadsheetId,
sheetName: effectiveSheetName,
cellRange: cellRange ? (cellRange as string).trim() : undefined,
values: parsedValues,
credential,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Microsoft Excel access token' },
spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' },
manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' },
sheetName: { type: 'string', description: 'Name of the sheet/tab' },
manualSheetName: { type: 'string', description: 'Manual sheet name entry' },
cellRange: { type: 'string', description: 'Cell range (e.g., A1:D10)' },
values: { type: 'string', description: 'Cell values data' },
valueInputOption: { type: 'string', description: 'Value input option' },
},
outputs: {
sheetName: {
type: 'string',
description: 'Name of the sheet',
condition: { field: 'operation', value: 'read' },
},
range: {
type: 'string',
description: 'Range that was read',
condition: { field: 'operation', value: 'read' },
},
values: {
type: 'json',
description: 'Cell values as 2D array',
condition: { field: 'operation', value: 'read' },
},
updatedRange: {
type: 'string',
description: 'Updated range',
condition: { field: 'operation', value: 'write' },
},
updatedRows: {
type: 'number',
description: 'Updated rows count',
condition: { field: 'operation', value: 'write' },
},
updatedColumns: {
type: 'number',
description: 'Updated columns count',
condition: { field: 'operation', value: 'write' },
},
updatedCells: {
type: 'number',
description: 'Updated cells count',
condition: { field: 'operation', value: 'write' },
},
metadata: { type: 'json', description: 'Spreadsheet metadata including ID and URL' },
},
}

View File

@@ -41,6 +41,7 @@ import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
import { GoogleGroupsBlock } from '@/blocks/blocks/google_groups'
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
import { GoogleSheetsV2Block } from '@/blocks/blocks/google_sheets_v2'
import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides'
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
import { GrafanaBlock } from '@/blocks/blocks/grafana'
@@ -73,6 +74,7 @@ import { McpBlock } from '@/blocks/blocks/mcp'
import { Mem0Block } from '@/blocks/blocks/mem0'
import { MemoryBlock } from '@/blocks/blocks/memory'
import { MicrosoftExcelBlock } from '@/blocks/blocks/microsoft_excel'
import { MicrosoftExcelV2Block } from '@/blocks/blocks/microsoft_excel_v2'
import { MicrosoftPlannerBlock } from '@/blocks/blocks/microsoft_planner'
import { MicrosoftTeamsBlock } from '@/blocks/blocks/microsoft_teams'
import { MistralParseBlock } from '@/blocks/blocks/mistral_parse'
@@ -200,6 +202,7 @@ export const registry: Record<string, BlockConfig> = {
google_forms: GoogleFormsBlock,
google_search: GoogleSearchBlock,
google_sheets: GoogleSheetsBlock,
google_sheets_v2: GoogleSheetsV2Block,
google_slides: GoogleSlidesBlock,
google_vault: GoogleVaultBlock,
google_groups: GoogleGroupsBlock,
@@ -230,6 +233,7 @@ export const registry: Record<string, BlockConfig> = {
mem0: Mem0Block,
memory: MemoryBlock,
microsoft_excel: MicrosoftExcelBlock,
microsoft_excel_v2: MicrosoftExcelV2Block,
microsoft_planner: MicrosoftPlannerBlock,
microsoft_teams: MicrosoftTeamsBlock,
mistral_parse: MistralParseBlock,

View File

@@ -58,6 +58,7 @@ export type SubBlockType =
| 'webhook-config' // Webhook configuration
| 'schedule-info' // Schedule status display (next run, last ran, failure badge)
| 'file-selector' // File selector for Google Drive, etc.
| 'sheet-selector' // Sheet/tab selector for Google Sheets, Microsoft Excel
| 'project-selector' // Project selector for Jira, Discord, etc.
| 'channel-selector' // Channel selector for Slack, Discord, etc.
| 'user-selector' // User selector for Slack, etc.
@@ -90,6 +91,7 @@ export const SELECTOR_TYPES_HYDRATION_REQUIRED: SubBlockType[] = [
'channel-selector',
'user-selector',
'file-selector',
'sheet-selector',
'folder-selector',
'project-selector',
'knowledge-base-selector',

View File

@@ -602,6 +602,68 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
return { id: file.id, label: file.name }
},
},
'google.sheets': {
key: 'google.sheets',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'google.sheets',
context.credentialId ?? 'none',
context.spreadsheetId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'google.sheets')
if (!context.spreadsheetId) {
throw new Error('Missing spreadsheet ID for google.sheets selector')
}
const data = await fetchJson<{ sheets: { id: string; name: string }[] }>(
'/api/tools/google_sheets/sheets',
{
searchParams: {
credentialId,
spreadsheetId: context.spreadsheetId,
workflowId: context.workflowId,
},
}
)
return (data.sheets || []).map((sheet) => ({
id: sheet.id,
label: sheet.name,
}))
},
},
'microsoft.excel.sheets': {
key: 'microsoft.excel.sheets',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.excel.sheets',
context.credentialId ?? 'none',
context.spreadsheetId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.excel.sheets')
if (!context.spreadsheetId) {
throw new Error('Missing spreadsheet ID for microsoft.excel.sheets selector')
}
const data = await fetchJson<{ sheets: { id: string; name: string }[] }>(
'/api/tools/microsoft_excel/sheets',
{
searchParams: {
credentialId,
spreadsheetId: context.spreadsheetId,
workflowId: context.workflowId,
},
}
)
return (data.sheets || []).map((sheet) => ({
id: sheet.id,
label: sheet.name,
}))
},
},
'microsoft.excel': {
key: 'microsoft.excel',
staleTime: SELECTOR_STALE,

View File

@@ -17,6 +17,7 @@ export interface SelectorResolutionArgs {
knowledgeBaseId?: string
siteId?: string
collectionId?: string
spreadsheetId?: string
}
const defaultContext: SelectorContext = {}
@@ -28,6 +29,8 @@ export function resolveSelectorForSubBlock(
switch (subBlock.type) {
case 'file-selector':
return resolveFileSelector(subBlock, args)
case 'sheet-selector':
return resolveSheetSelector(subBlock, args)
case 'folder-selector':
return resolveFolderSelector(subBlock, args)
case 'channel-selector':
@@ -58,6 +61,7 @@ function buildBaseContext(
knowledgeBaseId: args.knowledgeBaseId,
siteId: args.siteId,
collectionId: args.collectionId,
spreadsheetId: args.spreadsheetId,
...extra,
}
}
@@ -125,6 +129,23 @@ function resolveFileSelector(
}
}
function resolveSheetSelector(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs
): SelectorResolution {
const serviceId = subBlock.serviceId
const context = buildBaseContext(args)
switch (serviceId) {
case 'google-sheets':
return { key: 'google.sheets', context, allowSearch: false }
case 'microsoft-excel':
return { key: 'microsoft.excel.sheets', context, allowSearch: false }
default:
return { key: null, context, allowSearch: false }
}
}
function resolveFolderSelector(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs

View File

@@ -20,9 +20,11 @@ export type SelectorKey =
| 'onedrive.folders'
| 'sharepoint.sites'
| 'microsoft.excel'
| 'microsoft.excel.sheets'
| 'microsoft.word'
| 'microsoft.planner'
| 'google.drive'
| 'google.sheets'
| 'knowledge.documents'
| 'webflow.sites'
| 'webflow.collections'
@@ -49,6 +51,7 @@ export interface SelectorContext {
fileId?: string
siteId?: string
collectionId?: string
spreadsheetId?: string
}
export interface SelectorQueryArgs {

View File

@@ -0,0 +1,199 @@
import type {
GoogleSheetsV2AppendResponse,
GoogleSheetsV2ToolParams,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const appendV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2AppendResponse> = {
id: 'google_sheets_append_v2',
name: 'Append to Google Sheets V2',
description: 'Append data to the end of a specific sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet to append to',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to append to',
},
values: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'The data to append as a 2D array (e.g. [["Alice", 30], ["Bob", 25]]) or array of objects.',
},
valueInputOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The format of the data to append',
},
insertDataOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'How to insert the data (OVERWRITE or INSERT_ROWS)',
},
includeValuesInResponse: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Whether to include the appended values in the response',
},
},
request: {
url: (params) => {
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const url = new URL(
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(sheetName)}:append`
)
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
url.searchParams.append('valueInputOption', valueInputOption)
if (params.insertDataOption) {
url.searchParams.append('insertDataOption', params.insertDataOption)
}
if (params.includeValuesInResponse) {
url.searchParams.append('includeValuesInResponse', 'true')
}
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let processedValues: any = params.values || []
if (typeof processedValues === 'string') {
try {
processedValues = JSON.parse(processedValues)
} catch (_error) {
try {
const sanitizedInput = (processedValues as string)
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
processedValues = JSON.parse(sanitizedInput)
} catch (_secondError) {
processedValues = [[processedValues]]
}
}
}
if (
Array.isArray(processedValues) &&
processedValues.length > 0 &&
typeof processedValues[0] === 'object' &&
!Array.isArray(processedValues[0])
) {
const allKeys = new Set<string>()
processedValues.forEach((obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = processedValues.map((obj: any) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = obj[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : value
})
})
processedValues = [headers, ...rows]
} else if (!Array.isArray(processedValues)) {
processedValues = [[String(processedValues)]]
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
processedValues = (processedValues as any[]).map((row: any) =>
Array.isArray(row) ? row : [String(row)]
)
}
const body: Record<string, any> = {
majorDimension: params.majorDimension || 'ROWS',
values: processedValues,
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const metadata = {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
}
return {
success: true,
output: {
tableRange: data.tableRange ?? '',
updatedRange: data.updates?.updatedRange ?? '',
updatedRows: data.updates?.updatedRows ?? 0,
updatedColumns: data.updates?.updatedColumns ?? 0,
updatedCells: data.updates?.updatedCells ?? 0,
metadata: {
spreadsheetId: metadata.spreadsheetId,
spreadsheetUrl: metadata.spreadsheetUrl,
},
},
}
},
outputs: {
tableRange: { type: 'string', description: 'Range of the table where data was appended' },
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
updatedRows: { type: 'number', description: 'Number of rows updated' },
updatedColumns: { type: 'number', description: 'Number of columns updated' },
updatedCells: { type: 'number', description: 'Number of cells updated' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -1,9 +1,20 @@
import { appendTool } from '@/tools/google_sheets/append'
import { appendV2Tool } from '@/tools/google_sheets/append_v2'
import { readTool } from '@/tools/google_sheets/read'
import { readV2Tool } from '@/tools/google_sheets/read_v2'
import { updateTool } from '@/tools/google_sheets/update'
import { updateV2Tool } from '@/tools/google_sheets/update_v2'
import { writeTool } from '@/tools/google_sheets/write'
import { writeV2Tool } from '@/tools/google_sheets/write_v2'
// V1 exports
export const googleSheetsReadTool = readTool
export const googleSheetsWriteTool = writeTool
export const googleSheetsUpdateTool = updateTool
export const googleSheetsAppendTool = appendTool
// V2 exports
export const googleSheetsReadV2Tool = readV2Tool
export const googleSheetsWriteV2Tool = writeV2Tool
export const googleSheetsUpdateV2Tool = updateV2Tool
export const googleSheetsAppendV2Tool = appendV2Tool

View File

@@ -0,0 +1,113 @@
import type {
GoogleSheetsV2ReadResponse,
GoogleSheetsV2ToolParams,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const readV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2ReadResponse> = {
id: 'google_sheets_read_v2',
name: 'Read from Google Sheets V2',
description: 'Read data from a specific sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to read from',
},
cellRange: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The cell range to read (e.g. "A1:D10"). Defaults to "A1:Z1000" if not specified.',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const cellRange = params.cellRange?.trim() || 'A1:Z1000'
const fullRange = `${sheetName}!${cellRange}`
return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(fullRange)}`
},
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response, params?: GoogleSheetsV2ToolParams) => {
const data = await response.json()
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const metadata = {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
}
return {
success: true,
output: {
sheetName: params?.sheetName ?? '',
range: data.range ?? '',
values: data.values ?? [],
metadata: {
spreadsheetId: metadata.spreadsheetId,
spreadsheetUrl: metadata.spreadsheetUrl,
},
},
}
},
outputs: {
sheetName: { type: 'string', description: 'Name of the sheet that was read' },
range: { type: 'string', description: 'The range of cells that was read' },
values: { type: 'array', description: 'The cell values as a 2D array' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -75,3 +75,64 @@ export type GoogleSheetsResponse =
| GoogleSheetsWriteResponse
| GoogleSheetsUpdateResponse
| GoogleSheetsAppendResponse
// V2 Types - with explicit sheetName parameter
export interface GoogleSheetsV2ReadResponse extends ToolResponse {
output: {
sheetName: string
range: string
values: any[][]
metadata: GoogleSheetsMetadata
}
}
export interface GoogleSheetsV2WriteResponse extends ToolResponse {
output: {
updatedRange: string
updatedRows: number
updatedColumns: number
updatedCells: number
metadata: GoogleSheetsMetadata
}
}
export interface GoogleSheetsV2UpdateResponse extends ToolResponse {
output: {
updatedRange: string
updatedRows: number
updatedColumns: number
updatedCells: number
metadata: GoogleSheetsMetadata
}
}
export interface GoogleSheetsV2AppendResponse extends ToolResponse {
output: {
tableRange: string
updatedRange: string
updatedRows: number
updatedColumns: number
updatedCells: number
metadata: GoogleSheetsMetadata
}
}
export interface GoogleSheetsV2ToolParams {
accessToken: string
spreadsheetId: string
sheetName: string
cellRange?: string
values?: any[][]
valueInputOption?: 'RAW' | 'USER_ENTERED'
insertDataOption?: 'OVERWRITE' | 'INSERT_ROWS'
includeValuesInResponse?: boolean
responseValueRenderOption?: 'FORMATTED_VALUE' | 'UNFORMATTED_VALUE' | 'FORMULA'
majorDimension?: 'ROWS' | 'COLUMNS'
}
export type GoogleSheetsV2Response =
| GoogleSheetsV2ReadResponse
| GoogleSheetsV2WriteResponse
| GoogleSheetsV2UpdateResponse
| GoogleSheetsV2AppendResponse

View File

@@ -0,0 +1,185 @@
import type {
GoogleSheetsV2ToolParams,
GoogleSheetsV2UpdateResponse,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const updateV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2UpdateResponse> = {
id: 'google_sheets_update_v2',
name: 'Update Google Sheets V2',
description: 'Update data in a specific sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet to update',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to update',
},
cellRange: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The cell range to update (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
},
values: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'The data to update as a 2D array (e.g. [["Name", "Age"], ["Alice", 30]]) or array of objects.',
},
valueInputOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The format of the data to update',
},
includeValuesInResponse: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Whether to include the updated values in the response',
},
},
request: {
url: (params) => {
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const cellRange = params.cellRange?.trim() || 'A1'
const fullRange = `${sheetName}!${cellRange}`
const url = new URL(
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
)
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
url.searchParams.append('valueInputOption', valueInputOption)
if (params.includeValuesInResponse) {
url.searchParams.append('includeValuesInResponse', 'true')
}
return url.toString()
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let processedValues: any = params.values || []
// Minimal shape enforcement: Google requires a 2D array
if (!Array.isArray(processedValues)) {
processedValues = [[processedValues]]
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
processedValues = (processedValues as any[]).map((row: any) =>
Array.isArray(row) ? row : [row]
)
}
// Handle array of objects
if (
Array.isArray(processedValues) &&
processedValues.length > 0 &&
typeof processedValues[0] === 'object' &&
!Array.isArray(processedValues[0])
) {
const allKeys = new Set<string>()
processedValues.forEach((obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = processedValues.map((obj: any) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = obj[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : value
})
})
processedValues = [headers, ...rows]
}
const body: Record<string, any> = {
majorDimension: params.majorDimension || 'ROWS',
values: processedValues,
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const metadata = {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
}
return {
success: true,
output: {
updatedRange: data.updatedRange ?? null,
updatedRows: data.updatedRows ?? 0,
updatedColumns: data.updatedColumns ?? 0,
updatedCells: data.updatedCells ?? 0,
metadata: {
spreadsheetId: metadata.spreadsheetId,
spreadsheetUrl: metadata.spreadsheetUrl,
},
},
}
},
outputs: {
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
updatedRows: { type: 'number', description: 'Number of rows updated' },
updatedColumns: { type: 'number', description: 'Number of columns updated' },
updatedCells: { type: 'number', description: 'Number of cells updated' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -0,0 +1,177 @@
import type {
GoogleSheetsV2ToolParams,
GoogleSheetsV2WriteResponse,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const writeV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2WriteResponse> = {
id: 'google_sheets_write_v2',
name: 'Write to Google Sheets V2',
description: 'Write data to a specific sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to write to',
},
cellRange: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The cell range to write to (e.g. "A1:D10", "A1"). Defaults to "A1" if not specified.',
},
values: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'The data to write as a 2D array (e.g. [["Name", "Age"], ["Alice", 30], ["Bob", 25]]) or array of objects.',
},
valueInputOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The format of the data to write',
},
includeValuesInResponse: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Whether to include the written values in the response',
},
},
request: {
url: (params) => {
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
// Build the range: SheetName!CellRange or just SheetName!A1
const cellRange = params.cellRange?.trim() || 'A1'
const fullRange = `${sheetName}!${cellRange}`
const url = new URL(
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(fullRange)}`
)
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
url.searchParams.append('valueInputOption', valueInputOption)
if (params.includeValuesInResponse) {
url.searchParams.append('includeValuesInResponse', 'true')
}
return url.toString()
},
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let processedValues: any = params.values || []
// Handle array of objects
if (
Array.isArray(processedValues) &&
processedValues.length > 0 &&
typeof processedValues[0] === 'object' &&
!Array.isArray(processedValues[0])
) {
const allKeys = new Set<string>()
processedValues.forEach((obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = processedValues.map((obj: any) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = obj[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : value
})
})
processedValues = [headers, ...rows]
}
const body: Record<string, any> = {
majorDimension: params.majorDimension || 'ROWS',
values: processedValues,
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const metadata = {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
}
return {
success: true,
output: {
updatedRange: data.updatedRange ?? null,
updatedRows: data.updatedRows ?? 0,
updatedColumns: data.updatedColumns ?? 0,
updatedCells: data.updatedCells ?? 0,
metadata: {
spreadsheetId: metadata.spreadsheetId,
spreadsheetUrl: metadata.spreadsheetUrl,
},
},
}
},
outputs: {
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
updatedRows: { type: 'number', description: 'Number of rows updated' },
updatedColumns: { type: 'number', description: 'Number of columns updated' },
updatedCells: { type: 'number', description: 'Number of cells updated' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -1,9 +1,16 @@
import { readTool } from '@/tools/microsoft_excel/read'
import { readV2Tool } from '@/tools/microsoft_excel/read_v2'
import { tableAddTool } from '@/tools/microsoft_excel/table_add'
import { worksheetAddTool } from '@/tools/microsoft_excel/worksheet_add'
import { writeTool } from '@/tools/microsoft_excel/write'
import { writeV2Tool } from '@/tools/microsoft_excel/write_v2'
// V1 exports
export const microsoftExcelReadTool = readTool
export const microsoftExcelTableAddTool = tableAddTool
export const microsoftExcelWorksheetAddTool = worksheetAddTool
export const microsoftExcelWriteTool = writeTool
// V2 exports
export const microsoftExcelReadV2Tool = readV2Tool
export const microsoftExcelWriteV2Tool = writeV2Tool

View File

@@ -0,0 +1,133 @@
import type {
ExcelCellValue,
MicrosoftExcelV2ReadResponse,
MicrosoftExcelV2ToolParams,
} from '@/tools/microsoft_excel/types'
import {
getSpreadsheetWebUrl,
trimTrailingEmptyRowsAndColumns,
} from '@/tools/microsoft_excel/utils'
import type { ToolConfig } from '@/tools/types'
export const readV2Tool: ToolConfig<MicrosoftExcelV2ToolParams, MicrosoftExcelV2ReadResponse> = {
id: 'microsoft_excel_read_v2',
name: 'Read from Microsoft Excel V2',
description: 'Read data from a specific sheet in a Microsoft Excel spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'microsoft-excel',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Microsoft Excel API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet to read from',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to read from',
},
cellRange: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The cell range to read (e.g., "A1:D10"). If not specified, reads the entire used range.',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const encodedSheetName = encodeURIComponent(sheetName)
// If no cell range specified, fetch usedRange
if (!params.cellRange) {
return `https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}/workbook/worksheets('${encodedSheetName}')/usedRange(valuesOnly=true)`
}
const cellRange = params.cellRange.trim()
const encodedAddress = encodeURIComponent(cellRange)
return `https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}/workbook/worksheets('${encodedSheetName}')/range(address='${encodedAddress}')`
},
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response, params?: MicrosoftExcelV2ToolParams) => {
const data = await response.json()
const urlParts = response.url.split('/drive/items/')
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const accessToken = params?.accessToken
if (!accessToken) {
throw new Error('Access token is required')
}
const webUrl = await getSpreadsheetWebUrl(spreadsheetId, accessToken)
const address: string = data.address || data.addressLocal || ''
const rawValues: ExcelCellValue[][] = data.values || []
const values = trimTrailingEmptyRowsAndColumns(rawValues)
// Extract sheet name from address (format: SheetName!A1:B2)
const sheetName = params?.sheetName || address.split('!')[0] || ''
return {
success: true,
output: {
sheetName,
range: address,
values,
metadata: {
spreadsheetId,
spreadsheetUrl: webUrl,
},
},
}
},
outputs: {
sheetName: { type: 'string', description: 'Name of the sheet that was read' },
range: { type: 'string', description: 'The range that was read' },
values: { type: 'array', description: 'Array of rows containing cell values' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Microsoft Excel spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -91,3 +91,42 @@ export type MicrosoftExcelResponse =
| MicrosoftExcelWriteResponse
| MicrosoftExcelTableAddResponse
| MicrosoftExcelWorksheetAddResponse
// V2 Types - with separate sheetName param
export interface MicrosoftExcelV2ToolParams {
accessToken: string
spreadsheetId: string
sheetName: string
cellRange?: string
values?: ExcelCellValue[][]
valueInputOption?: 'RAW' | 'USER_ENTERED'
includeValuesInResponse?: boolean
majorDimension?: 'ROWS' | 'COLUMNS'
}
export interface MicrosoftExcelV2ReadResponse extends ToolResponse {
output: {
sheetName: string
range: string
values: ExcelCellValue[][]
metadata: {
spreadsheetId: string
spreadsheetUrl: string
}
}
}
export interface MicrosoftExcelV2WriteResponse extends ToolResponse {
output: {
updatedRange: string | null
updatedRows: number
updatedColumns: number
updatedCells: number
metadata: {
spreadsheetId: string
spreadsheetUrl: string
}
}
}
export type MicrosoftExcelV2Response = MicrosoftExcelV2ReadResponse | MicrosoftExcelV2WriteResponse

View File

@@ -0,0 +1,184 @@
import type {
MicrosoftExcelV2ToolParams,
MicrosoftExcelV2WriteResponse,
} from '@/tools/microsoft_excel/types'
import { getSpreadsheetWebUrl } from '@/tools/microsoft_excel/utils'
import type { ToolConfig } from '@/tools/types'
export const writeV2Tool: ToolConfig<MicrosoftExcelV2ToolParams, MicrosoftExcelV2WriteResponse> = {
id: 'microsoft_excel_write_v2',
name: 'Write to Microsoft Excel V2',
description: 'Write data to a specific sheet in a Microsoft Excel spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'microsoft-excel',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Microsoft Excel API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet to write to',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to write to',
},
cellRange: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The cell range to write to (e.g., "A1:D10", "A1"). Defaults to "A1" if not specified.',
},
values: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'The data to write as a 2D array (e.g. [["Name", "Age"], ["Alice", 30], ["Bob", 25]]) or array of objects.',
},
valueInputOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The format of the data to write',
},
includeValuesInResponse: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Whether to include the written values in the response',
},
},
request: {
url: (params) => {
const spreadsheetId = params.spreadsheetId?.trim()
if (!spreadsheetId) {
throw new Error('Spreadsheet ID is required')
}
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const cellRange = params.cellRange?.trim() || 'A1'
const encodedSheetName = encodeURIComponent(sheetName)
const encodedAddress = encodeURIComponent(cellRange)
const url = new URL(
`https://graph.microsoft.com/v1.0/me/drive/items/${spreadsheetId}/workbook/worksheets('${encodedSheetName}')/range(address='${encodedAddress}')`
)
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
url.searchParams.append('valueInputOption', valueInputOption)
if (params.includeValuesInResponse) {
url.searchParams.append('includeValuesInResponse', 'true')
}
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let processedValues: any = params.values || []
// Handle array of objects
if (
Array.isArray(processedValues) &&
processedValues.length > 0 &&
typeof processedValues[0] === 'object' &&
!Array.isArray(processedValues[0])
) {
const allKeys = new Set<string>()
processedValues.forEach((obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = processedValues.map((obj: any) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = obj[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : value
})
})
processedValues = [headers, ...rows]
}
const body: Record<string, any> = {
majorDimension: params.majorDimension || 'ROWS',
values: processedValues,
}
return body
},
},
transformResponse: async (response: Response, params?: MicrosoftExcelV2ToolParams) => {
const data = await response.json()
const urlParts = response.url.split('/drive/items/')
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const accessToken = params?.accessToken
if (!accessToken) {
throw new Error('Access token is required')
}
const webUrl = await getSpreadsheetWebUrl(spreadsheetId, accessToken)
return {
success: true,
output: {
updatedRange: data.address ?? null,
updatedRows: data.rowCount ?? 0,
updatedColumns: data.columnCount ?? 0,
updatedCells: (data.rowCount ?? 0) * (data.columnCount ?? 0),
metadata: {
spreadsheetId,
spreadsheetUrl: webUrl,
},
},
}
},
outputs: {
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
updatedRows: { type: 'number', description: 'Number of rows updated' },
updatedColumns: { type: 'number', description: 'Number of columns updated' },
updatedCells: { type: 'number', description: 'Number of cells updated' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Microsoft Excel spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

View File

@@ -416,9 +416,13 @@ import {
} from '@/tools/google_groups'
import {
googleSheetsAppendTool,
googleSheetsAppendV2Tool,
googleSheetsReadTool,
googleSheetsReadV2Tool,
googleSheetsUpdateTool,
googleSheetsUpdateV2Tool,
googleSheetsWriteTool,
googleSheetsWriteV2Tool,
} from '@/tools/google_sheets'
import {
googleSlidesAddImageTool,
@@ -827,9 +831,11 @@ import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from
import { memoryAddTool, memoryDeleteTool, memoryGetAllTool, memoryGetTool } from '@/tools/memory'
import {
microsoftExcelReadTool,
microsoftExcelReadV2Tool,
microsoftExcelTableAddTool,
microsoftExcelWorksheetAddTool,
microsoftExcelWriteTool,
microsoftExcelWriteV2Tool,
} from '@/tools/microsoft_excel'
import {
microsoftPlannerCreateBucketTool,
@@ -2107,6 +2113,10 @@ export const tools: Record<string, ToolConfig> = {
google_sheets_write: googleSheetsWriteTool,
google_sheets_update: googleSheetsUpdateTool,
google_sheets_append: googleSheetsAppendTool,
google_sheets_read_v2: googleSheetsReadV2Tool,
google_sheets_write_v2: googleSheetsWriteV2Tool,
google_sheets_update_v2: googleSheetsUpdateV2Tool,
google_sheets_append_v2: googleSheetsAppendV2Tool,
google_slides_read: googleSlidesReadTool,
google_slides_write: googleSlidesWriteTool,
google_slides_create: googleSlidesCreateTool,
@@ -2478,6 +2488,8 @@ export const tools: Record<string, ToolConfig> = {
microsoft_excel_write: microsoftExcelWriteTool,
microsoft_excel_table_add: microsoftExcelTableAddTool,
microsoft_excel_worksheet_add: microsoftExcelWorksheetAddTool,
microsoft_excel_read_v2: microsoftExcelReadV2Tool,
microsoft_excel_write_v2: microsoftExcelWriteV2Tool,
microsoft_planner_create_task: microsoftPlannerCreateTaskTool,
microsoft_planner_read_task: microsoftPlannerReadTaskTool,
microsoft_planner_update_task: microsoftPlannerUpdateTaskTool,

View File

@@ -206,6 +206,9 @@ ${mappingEntries}
function extractAllBlockConfigs(fileContent: string): BlockConfig[] {
const configs: BlockConfig[] = []
// First, extract the primary icon from the file (for V2 blocks that inherit via spread)
const primaryIcon = extractIconName(fileContent)
// Find all block exports in the file
const exportRegex = /export\s+const\s+(\w+)Block\s*:\s*BlockConfig[^=]*=\s*\{/g
let match
@@ -234,8 +237,13 @@ function extractAllBlockConfigs(fileContent: string): BlockConfig[] {
continue
}
const config = extractBlockConfigFromContent(blockContent, blockName)
// Pass fileContent to enable spread inheritance resolution
const config = extractBlockConfigFromContent(blockContent, blockName, fileContent)
if (config) {
// For V2 blocks that don't have an explicit icon, use the primary icon from the file
if (!config.iconName && primaryIcon) {
;(config as any).iconName = primaryIcon
}
configs.push(config)
}
}
@@ -244,26 +252,102 @@ function extractAllBlockConfigs(fileContent: string): BlockConfig[] {
return configs
}
/**
* Extract the name of the spread base block (e.g., "GitHubBlock" from "...GitHubBlock")
*/
function extractSpreadBase(blockContent: string): string | null {
const spreadMatch = blockContent.match(/^\s*\.\.\.(\w+Block)\s*,/m)
return spreadMatch ? spreadMatch[1] : null
}
/**
* Extract block config from a specific block's content
* If the block uses spread inheritance (e.g., ...GitHubBlock), attempts to resolve
* missing properties from the base block in the file content.
*/
function extractBlockConfigFromContent(
blockContent: string,
blockName: string
blockName: string,
fileContent?: string
): BlockConfig | null {
try {
// Check for spread inheritance
const spreadBase = extractSpreadBase(blockContent)
let baseConfig: BlockConfig | null = null
if (spreadBase && fileContent) {
// Extract the base block's content from the file
const baseBlockRegex = new RegExp(
`export\\s+const\\s+${spreadBase}\\s*:\\s*BlockConfig[^=]*=\\s*\\{`,
'g'
)
const baseMatch = baseBlockRegex.exec(fileContent)
if (baseMatch) {
const startIndex = baseMatch.index + baseMatch[0].length - 1
let braceCount = 1
let endIndex = startIndex + 1
while (endIndex < fileContent.length && braceCount > 0) {
if (fileContent[endIndex] === '{') braceCount++
else if (fileContent[endIndex] === '}') braceCount--
endIndex++
}
if (braceCount === 0) {
const baseBlockContent = fileContent.substring(startIndex, endIndex)
// Recursively extract base config (but don't pass fileContent to avoid infinite loops)
baseConfig = extractBlockConfigFromContent(
baseBlockContent,
spreadBase.replace('Block', '')
)
}
}
}
// Extract properties from this block, using topLevelOnly=true for main properties
const blockType =
extractStringPropertyFromContent(blockContent, 'type') || blockName.toLowerCase()
const name = extractStringPropertyFromContent(blockContent, 'name') || `${blockName} Block`
const description = extractStringPropertyFromContent(blockContent, 'description') || ''
const longDescription = extractStringPropertyFromContent(blockContent, 'longDescription') || ''
const category = extractStringPropertyFromContent(blockContent, 'category') || 'misc'
const bgColor = extractStringPropertyFromContent(blockContent, 'bgColor') || '#F5F5F5'
const iconName = extractIconNameFromContent(blockContent) || ''
extractStringPropertyFromContent(blockContent, 'type', true) || blockName.toLowerCase()
const name =
extractStringPropertyFromContent(blockContent, 'name', true) ||
baseConfig?.name ||
`${blockName} Block`
const description =
extractStringPropertyFromContent(blockContent, 'description', true) ||
baseConfig?.description ||
''
const longDescription =
extractStringPropertyFromContent(blockContent, 'longDescription', true) ||
baseConfig?.longDescription ||
''
const category =
extractStringPropertyFromContent(blockContent, 'category', true) ||
baseConfig?.category ||
'misc'
const bgColor =
extractStringPropertyFromContent(blockContent, 'bgColor', true) ||
baseConfig?.bgColor ||
'#F5F5F5'
const iconName = extractIconNameFromContent(blockContent) || (baseConfig as any)?.iconName || ''
const outputs = extractOutputsFromContent(blockContent)
const toolsAccess = extractToolsAccessFromContent(blockContent)
// For tools.access, if not found directly, check if it's derived from base via map
let finalToolsAccess = toolsAccess
if (toolsAccess.length === 0 && baseConfig?.tools?.access) {
// Check if there's a map operation on base tools
// Pattern: access: (SomeBlock.tools?.access || []).map((toolId) => `${toolId}_v2`)
const mapMatch = blockContent.match(
/access\s*:\s*\(\s*\w+Block\.tools\?\.access\s*\|\|\s*\[\]\s*\)\.map\s*\(\s*\(\s*\w+\s*\)\s*=>\s*`\$\{\s*\w+\s*\}_v(\d+)`\s*\)/
)
if (mapMatch) {
// V2 block - append the version suffix to base tools
const versionSuffix = `_v${mapMatch[1]}`
finalToolsAccess = baseConfig.tools.access.map((tool) => `${tool}${versionSuffix}`)
}
}
return {
type: blockType,
name,
@@ -274,7 +358,7 @@ function extractBlockConfigFromContent(
iconName,
outputs,
tools: {
access: toolsAccess,
access: finalToolsAccess.length > 0 ? finalToolsAccess : baseConfig?.tools?.access || [],
},
}
} catch (error) {
@@ -291,14 +375,45 @@ function stripVersionSuffix(type: string): string {
return type.replace(/_v\d+$/, '')
}
function extractStringPropertyFromContent(content: string, propName: string): string | null {
const singleQuoteMatch = content.match(new RegExp(`${propName}\\s*:\\s*'([^']*)'`, 'm'))
/**
* Extract a string property from block content.
* For top-level properties like 'description', only looks in the portion before nested objects
* to avoid matching properties inside nested structures like outputs.
*/
function extractStringPropertyFromContent(
content: string,
propName: string,
topLevelOnly = false
): string | null {
let searchContent = content
// For top-level properties, only search before nested objects like outputs, tools, inputs, subBlocks
if (topLevelOnly) {
const nestedObjectPatterns = [
/\boutputs\s*:\s*\{/,
/\btools\s*:\s*\{/,
/\binputs\s*:\s*\{/,
/\bsubBlocks\s*:\s*\[/,
/\btriggers\s*:\s*\{/,
]
let cutoffIndex = content.length
for (const pattern of nestedObjectPatterns) {
const match = content.match(pattern)
if (match && match.index !== undefined && match.index < cutoffIndex) {
cutoffIndex = match.index
}
}
searchContent = content.substring(0, cutoffIndex)
}
const singleQuoteMatch = searchContent.match(new RegExp(`${propName}\\s*:\\s*'([^']*)'`, 'm'))
if (singleQuoteMatch) return singleQuoteMatch[1]
const doubleQuoteMatch = content.match(new RegExp(`${propName}\\s*:\\s*"([^"]*)"`, 'm'))
const doubleQuoteMatch = searchContent.match(new RegExp(`${propName}\\s*:\\s*"([^"]*)"`, 'm'))
if (doubleQuoteMatch) return doubleQuoteMatch[1]
const templateMatch = content.match(new RegExp(`${propName}\\s*:\\s*\`([^\`]+)\``, 's'))
const templateMatch = searchContent.match(new RegExp(`${propName}\\s*:\\s*\`([^\`]+)\``, 's'))
if (templateMatch) {
let templateContent = templateMatch[1]
templateContent = templateContent.replace(/\$\{[^}]+\}/g, '')
@@ -635,8 +750,48 @@ function extractToolInfo(
const toolConfigMatch = fileContent.match(toolConfigRegex)
// Description should come from the specific tool block if found
// Only search before nested objects (params, outputs, request, etc.) to avoid matching
// descriptions inside outputs or params
let descriptionSearchContent = toolContent
const nestedObjectPatterns = [
/\bparams\s*:\s*[{]/,
/\boutputs\s*:\s*\{/,
/\brequest\s*:\s*\{/,
/\boauth\s*:\s*\{/,
/\btransformResponse\s*:/,
]
let cutoffIndex = toolContent.length
for (const pattern of nestedObjectPatterns) {
const match = toolContent.match(pattern)
if (match && match.index !== undefined && match.index < cutoffIndex) {
cutoffIndex = match.index
}
}
descriptionSearchContent = toolContent.substring(0, cutoffIndex)
const descriptionRegex = /description\s*:\s*['"](.*?)['"].*/
const descriptionMatch = toolContent.match(descriptionRegex)
let descriptionMatch = descriptionSearchContent.match(descriptionRegex)
// If description isn't found as a literal (might be inherited like description: baseTool.description),
// try to find the referenced tool's description
if (!descriptionMatch) {
const inheritedDescMatch = descriptionSearchContent.match(
/description\s*:\s*(\w+)Tool\.description/
)
if (inheritedDescMatch) {
const baseTool = inheritedDescMatch[1]
// Try to find the base tool's description in the file
const baseToolDescRegex = new RegExp(
`export\\s+const\\s+${baseTool}Tool[^{]*\\{[\\s\\S]*?description\\s*:\\s*['"]([^'"]+)['"]`,
'i'
)
const baseToolMatch = fileContent.match(baseToolDescRegex)
if (baseToolMatch) {
descriptionMatch = baseToolMatch
}
}
}
const description = descriptionMatch ? descriptionMatch[1] : 'No description available'
const params: Array<{ name: string; type: string; required: boolean; description: string }> = []
@@ -1042,19 +1197,31 @@ async function getToolInfo(toolName: string): Promise<{
toolSuffix = parts.slice(1).join('_')
}
// Strip version suffix from tool suffix (V2 tools are in the same file as V1)
// Check if this is a versioned tool (e.g., _v2, _v3)
const isVersionedTool = /_v\d+$/.test(toolSuffix)
const strippedToolSuffix = stripVersionSuffix(toolSuffix)
const possibleLocations = []
const possibleLocations: Array<{ path: string; priority: 'exact' | 'fallback' }> = []
// Try stripped suffix first (e.g., launch_agent.ts for launch_agent_v2)
possibleLocations.push(
path.join(rootDir, `apps/sim/tools/${toolPrefix}/${strippedToolSuffix}.ts`)
)
// Also try original suffix in case the file actually has v2 in the name
if (strippedToolSuffix !== toolSuffix) {
possibleLocations.push(path.join(rootDir, `apps/sim/tools/${toolPrefix}/${toolSuffix}.ts`))
// For versioned tools, prioritize the exact versioned file first
// This handles cases like google_sheets where V2 is in a separate file (read_v2.ts)
if (isVersionedTool) {
// First priority: exact versioned file (e.g., read_v2.ts)
possibleLocations.push({
path: path.join(rootDir, `apps/sim/tools/${toolPrefix}/${toolSuffix}.ts`),
priority: 'exact',
})
// Second priority: stripped file that contains both V1 and V2 (e.g., pr.ts for github)
possibleLocations.push({
path: path.join(rootDir, `apps/sim/tools/${toolPrefix}/${strippedToolSuffix}.ts`),
priority: 'fallback',
})
} else {
// Non-versioned tool: try the direct file
possibleLocations.push({
path: path.join(rootDir, `apps/sim/tools/${toolPrefix}/${toolSuffix}.ts`),
priority: 'exact',
})
}
// Also try camelCase versions
@@ -1062,16 +1229,49 @@ async function getToolInfo(toolName: string): Promise<{
.split('_')
.map((part, i) => (i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
.join('')
possibleLocations.push(path.join(rootDir, `apps/sim/tools/${toolPrefix}/${camelCaseSuffix}.ts`))
possibleLocations.push({
path: path.join(rootDir, `apps/sim/tools/${toolPrefix}/${camelCaseSuffix}.ts`),
priority: 'fallback',
})
possibleLocations.push(path.join(rootDir, `apps/sim/tools/${toolPrefix}/index.ts`))
// Fall back to index.ts
possibleLocations.push({
path: path.join(rootDir, `apps/sim/tools/${toolPrefix}/index.ts`),
priority: 'fallback',
})
let toolFileContent = ''
let foundFile = ''
// Try to find a file that contains the exact tool ID
for (const location of possibleLocations) {
if (fs.existsSync(location)) {
toolFileContent = fs.readFileSync(location, 'utf-8')
break
if (fs.existsSync(location.path)) {
const content = fs.readFileSync(location.path, 'utf-8')
// Check if this file contains the exact tool ID we're looking for
const toolIdRegex = new RegExp(`id:\\s*['"]${toolName}['"]`)
if (toolIdRegex.test(content)) {
toolFileContent = content
foundFile = location.path
break
}
// For fallback locations, store the content in case we don't find an exact match
if (location.priority === 'fallback' && !toolFileContent) {
toolFileContent = content
foundFile = location.path
}
}
}
// If we didn't find a file with the exact ID, use the first available file
if (!toolFileContent) {
for (const location of possibleLocations) {
if (fs.existsSync(location.path)) {
toolFileContent = fs.readFileSync(location.path, 'utf-8')
foundFile = location.path
break
}
}
}
@@ -1416,10 +1616,17 @@ ${toolsSection}
}
/**
* Extract all hidden block types (blocks with hideFromToolbar: true)
* Extract all hidden block types (blocks with hideFromToolbar: true) and
* the set of display names that will be generated by visible blocks.
* This is needed to avoid deleting docs for hidden V1 blocks when a visible V2 block
* will regenerate them.
*/
async function getHiddenBlockTypes(): Promise<Set<string>> {
async function getHiddenAndVisibleBlockTypes(): Promise<{
hiddenTypes: Set<string>
visibleDisplayNames: Set<string>
}> {
const hiddenTypes = new Set<string>()
const visibleDisplayNames = new Set<string>()
const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort()
for (const blockFile of blockFiles) {
@@ -1444,25 +1651,29 @@ async function getHiddenBlockTypes(): Promise<Set<string>> {
if (braceCount === 0) {
const blockContent = fileContent.substring(startIndex, endIndex)
const blockType = extractStringPropertyFromContent(blockContent, 'type', true)
// Check if this block has hideFromToolbar: true
if (/hideFromToolbar\s*:\s*true/.test(blockContent)) {
const blockType = extractStringPropertyFromContent(blockContent, 'type')
if (blockType) {
if (blockType) {
// Check if this block has hideFromToolbar: true
if (/hideFromToolbar\s*:\s*true/.test(blockContent)) {
hiddenTypes.add(blockType)
} else {
// This block is visible - add its display name (stripped version)
visibleDisplayNames.add(stripVersionSuffix(blockType))
}
}
}
}
}
return hiddenTypes
return { hiddenTypes, visibleDisplayNames }
}
/**
* Remove documentation files for hidden blocks
* Remove documentation files for hidden blocks.
* Skips deletion if a visible V2 block will regenerate the docs.
*/
function cleanupHiddenBlockDocs(hiddenTypes: Set<string>): void {
function cleanupHiddenBlockDocs(hiddenTypes: Set<string>, visibleDisplayNames: Set<string>): void {
console.log('Cleaning up docs for hidden blocks...')
// Create a set of stripped hidden types (for matching doc files without version suffix)
@@ -1482,6 +1693,13 @@ function cleanupHiddenBlockDocs(hiddenTypes: Set<string>): void {
// Check both original type and stripped type (since doc files use stripped names)
if (hiddenTypes.has(blockType) || strippedHiddenTypes.has(blockType)) {
// Skip deletion if there's a visible V2 block that will regenerate this doc
// (e.g., don't delete intercom.mdx if IntercomV2Block is visible)
if (visibleDisplayNames.has(blockType)) {
console.log(` Skipping deletion of ${blockType}.mdx - visible V2 block will regenerate it`)
continue
}
const docPath = path.join(DOCS_OUTPUT_PATH, docFile)
fs.unlinkSync(docPath)
console.log(`✓ Removed docs for hidden block: ${blockType}`)
@@ -1505,12 +1723,12 @@ async function generateAllBlockDocs() {
const iconMapping = await generateIconMapping()
writeIconMapping(iconMapping)
// Get hidden block types before generating docs
const hiddenTypes = await getHiddenBlockTypes()
// Get hidden and visible block types before generating docs
const { hiddenTypes, visibleDisplayNames } = await getHiddenAndVisibleBlockTypes()
console.log(`Found ${hiddenTypes.size} hidden blocks: ${[...hiddenTypes].join(', ')}`)
// Clean up docs for hidden blocks
cleanupHiddenBlockDocs(hiddenTypes)
// Clean up docs for hidden blocks (skipping those with visible V2 equivalents)
cleanupHiddenBlockDocs(hiddenTypes, visibleDisplayNames)
const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort()