feat(tools): added 200+ new tools across confluence, discord, exa, firecrawl, jina, jira, linear, linkup, MS suite, parallel, reddit, supabase, & tavily (#1824)

* feat(tools): added 150+ new tools across confluence, discord, exa, firecrawl, jina, jira, linear, linkup, MS suite, parallel, reddit, supabase, & tavily

* feat(tools): added 150+ new tools across confluence, discord, exa, firecrawl, jina, jira, linear, linkup, MS suite, parallel, reddit, supabase, & tavily

* replace console.log and console.error with loggers instead

* cleanup

* update message change

* removed layout from all blocks with new operations

* cleanup

* fixed create row

* fixed full text search

* fixed firecrawl, fixed supabase download

* fix subblock name

* teams update

* finish supabase

* tested & updated parallel AI

* removed dates from Exa, fixed research

* fix jina

* more jina updates

* fixed tavily + tool-inp for empty dropdown items

* updated microsoft tools

* edited reddit

* added html/text options for gmail outlook resend tools, added linear and jira triggers

* reddit tool fix

* another reddit fix

* remove unused github utils, fix linear triggers

* added onedrive delete, fixed jira tools

* fixed confluence

* fix some linear operations, fixed jira triggers

* fix some linear tools

* fix build

* fix jira payloads

* fixe jira payload

* fix conflicts

* added sample payload

* fix stripe icon

* run lint
This commit is contained in:
Waleed
2025-11-07 13:03:57 -08:00
committed by GitHub
parent eb459d0ab9
commit c9a8c7e392
267 changed files with 12895 additions and 3550 deletions

View File

@@ -280,28 +280,6 @@ Delete an attachment from a Confluence page (moves to trash).
| `attachmentId` | string | Deleted attachment ID |
| `deleted` | boolean | Deletion status |
### `confluence_add_label`
Add a label to a Confluence page.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
| `pageId` | string | Yes | Confluence page ID to add label to |
| `labelName` | string | Yes | Label name to add |
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of operation |
| `pageId` | string | Page ID |
| `labelName` | string | Label name |
| `added` | boolean | Addition status |
### `confluence_list_labels`
List all labels on a Confluence page.
@@ -321,28 +299,6 @@ List all labels on a Confluence page.
| `ts` | string | Timestamp of retrieval |
| `labels` | array | List of labels |
### `confluence_remove_label`
Remove a label from a Confluence page.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
| `pageId` | string | Yes | Confluence page ID to remove label from |
| `labelName` | string | Yes | Label name to remove |
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of operation |
| `pageId` | string | Page ID |
| `labelName` | string | Label name |
| `removed` | boolean | Removal status |
### `confluence_get_space`
Get details about a specific Confluence space.

View File

@@ -64,10 +64,6 @@ Search the web using Exa AI. Returns relevant search results with titles, URLs,
| `type` | string | No | Search type: neural, keyword, auto or fast \(default: auto\) |
| `includeDomains` | string | No | Comma-separated list of domains to include in results |
| `excludeDomains` | string | No | Comma-separated list of domains to exclude from results |
| `startPublishedDate` | string | No | Filter results published after this date \(ISO 8601 format, e.g., 2024-01-01\) |
| `endPublishedDate` | string | No | Filter results published before this date \(ISO 8601 format\) |
| `startCrawlDate` | string | No | Filter results crawled after this date \(ISO 8601 format\) |
| `endCrawlDate` | string | No | Filter results crawled before this date \(ISO 8601 format\) |
| `category` | string | No | Filter by category: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `text` | boolean | No | Include full text content in results \(default: false\) |
| `highlights` | boolean | No | Include highlighted snippets in results \(default: false\) |
@@ -118,10 +114,6 @@ Find webpages similar to a given URL using Exa AI. Returns a list of similar lin
| `includeDomains` | string | No | Comma-separated list of domains to include in results |
| `excludeDomains` | string | No | Comma-separated list of domains to exclude from results |
| `excludeSourceDomain` | boolean | No | Exclude the source domain from results \(default: false\) |
| `startPublishedDate` | string | No | Filter results published after this date \(ISO 8601 format, e.g., 2024-01-01\) |
| `endPublishedDate` | string | No | Filter results published before this date \(ISO 8601 format\) |
| `startCrawlDate` | string | No | Filter results crawled after this date \(ISO 8601 format\) |
| `endCrawlDate` | string | No | Filter results crawled before this date \(ISO 8601 format\) |
| `category` | string | No | Filter by category: company, research_paper, news_article, pdf, github, tweet, movie, song, personal_site |
| `highlights` | boolean | No | Include highlighted snippets in results \(default: false\) |
| `summary` | boolean | No | Include AI-generated summaries in results \(default: false\) |

View File

@@ -59,7 +59,7 @@ This allows your agents to gather information from websites, extract structured
## Usage Instructions
Integrate Firecrawl into the workflow. Can scrape pages, search the web, crawl entire websites, map URL structures, and extract structured data using AI.
Integrate Firecrawl into the workflow. Scrape pages, search the web, crawl entire sites, map URL structures, and extract structured data with AI.
@@ -74,25 +74,7 @@ Extract structured content from web pages with comprehensive metadata support. C
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `url` | string | Yes | The URL to scrape content from |
| `formats` | json | No | Output formats \(markdown, html, rawHtml, links, images, screenshot\). Default: \["markdown"\] |
| `onlyMainContent` | boolean | No | Extract only main content, excluding headers, navs, footers \(default: true\) |
| `includeTags` | json | No | HTML tags to retain in the output |
| `excludeTags` | json | No | HTML tags to remove from the output |
| `maxAge` | number | No | Return cached version if younger than this age in ms \(default: 172800000\) |
| `headers` | json | No | Custom request headers \(cookies, user-agent, etc.\) |
| `waitFor` | number | No | Delay in milliseconds before fetching \(default: 0\) |
| `mobile` | boolean | No | Emulate mobile device \(default: false\) |
| `skipTlsVerification` | boolean | No | Skip TLS certificate verification \(default: true\) |
| `timeout` | number | No | Request timeout in milliseconds |
| `parsers` | json | No | File processing controls \(e.g., \["pdf"\]\) |
| `actions` | json | No | Pre-scrape operations \(wait, click, scroll, screenshot, etc.\) |
| `location` | json | No | Geographic settings \(country, languages\) |
| `removeBase64Images` | boolean | No | Strip base64 images from output \(default: true\) |
| `blockAds` | boolean | No | Enable ad and popup blocking \(default: true\) |
| `proxy` | string | No | Proxy type: basic, stealth, or auto \(default: auto\) |
| `storeInCache` | boolean | No | Cache the page \(default: true\) |
| `zeroDataRetention` | boolean | No | Enable zero data retention mode \(default: false\) |
| `scrapeOptions` | json | No | Options for content scraping \(legacy, prefer top-level params\) |
| `scrapeOptions` | json | No | Options for content scraping |
| `apiKey` | string | Yes | Firecrawl API key |
#### Output
@@ -112,15 +94,6 @@ Search for information on the web using Firecrawl
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | The search query to use |
| `limit` | number | No | Maximum number of results to return \(1-100, default: 5\) |
| `sources` | json | No | Search sources: \["web"\], \["images"\], or \["news"\] \(default: \["web"\]\) |
| `categories` | json | No | Filter by categories: \["github"\], \["research"\], or \["pdf"\] |
| `tbs` | string | No | Time-based search: qdr:h \(hour\), qdr:d \(day\), qdr:w \(week\), qdr:m \(month\), qdr:y \(year\) |
| `location` | string | No | Geographic location for results \(e.g., "San Francisco, California, United States"\) |
| `country` | string | No | ISO country code for geo-targeting \(default: US\) |
| `timeout` | number | No | Timeout in milliseconds \(default: 60000\) |
| `ignoreInvalidURLs` | boolean | No | Exclude invalid URLs from results \(default: false\) |
| `scrapeOptions` | json | No | Advanced scraping configuration for search results |
| `apiKey` | string | Yes | Firecrawl API key |
#### Output
@@ -140,20 +113,6 @@ Crawl entire websites and extract structured content from all accessible pages
| `url` | string | Yes | The website URL to crawl |
| `limit` | number | No | Maximum number of pages to crawl \(default: 100\) |
| `onlyMainContent` | boolean | No | Extract only main content from pages |
| `prompt` | string | No | Natural language instruction to auto-generate crawler options |
| `maxDiscoveryDepth` | number | No | Depth limit for URL discovery \(root pages have depth 0\) |
| `sitemap` | string | No | Whether to use sitemap data: "skip" or "include" \(default: "include"\) |
| `crawlEntireDomain` | boolean | No | Follow sibling/parent URLs or only child paths \(default: false\) |
| `allowExternalLinks` | boolean | No | Follow external website links \(default: false\) |
| `allowSubdomains` | boolean | No | Follow subdomain links \(default: false\) |
| `ignoreQueryParameters` | boolean | No | Prevent re-scraping same path with different query params \(default: false\) |
| `delay` | number | No | Seconds between scrapes for rate limit compliance |
| `maxConcurrency` | number | No | Concurrent scrape limit |
| `excludePaths` | json | No | Array of regex patterns for URLs to exclude |
| `includePaths` | json | No | Array of regex patterns for URLs to include exclusively |
| `webhook` | json | No | Webhook configuration for crawl notifications |
| `scrapeOptions` | json | No | Advanced scraping configuration |
| `zeroDataRetention` | boolean | No | Enable zero data retention \(default: false\) |
| `apiKey` | string | Yes | Firecrawl API Key |
#### Output

View File

@@ -76,6 +76,7 @@ Send emails using Gmail
| `to` | string | Yes | Recipient email address |
| `subject` | string | No | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type for the email body \(text or html\) |
| `threadId` | string | No | Thread ID to reply to \(for threading\) |
| `replyToMessageId` | string | No | Gmail message ID to reply to - use the "id" field from Gmail Read results \(not the RFC "messageId"\) |
| `cc` | string | No | CC recipients \(comma-separated\) |
@@ -100,6 +101,7 @@ Draft emails using Gmail
| `to` | string | Yes | Recipient email address |
| `subject` | string | No | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type for the email body \(text or html\) |
| `threadId` | string | No | Thread ID to reply to \(for threading\) |
| `replyToMessageId` | string | No | Gmail message ID to reply to - use the "id" field from Gmail Read results \(not the RFC "messageId"\) |
| `cc` | string | No | CC recipients \(comma-separated\) |

View File

@@ -82,25 +82,13 @@ Extract and process web content into clean, LLM-friendly text using Jina AI Read
| `gatherLinks` | boolean | No | Whether to gather all links at the end |
| `jsonResponse` | boolean | No | Whether to return response in JSON format |
| `apiKey` | string | Yes | Your Jina AI API key |
| `targetSelector` | string | No | CSS selector to target specific page elements \(e.g., "#main-content"\) |
| `waitForSelector` | string | No | CSS selector to wait for before extracting content \(useful for dynamic pages\) |
| `removeSelector` | string | No | CSS selector for elements to exclude \(e.g., "header, footer, .ad"\) |
| `timeout` | number | No | Maximum seconds to wait for page load |
| `withImagesummary` | boolean | No | Gather all images from the page with metadata |
| `retainImages` | string | No | Control image inclusion: "none" removes all, "all" keeps all |
| `returnFormat` | string | No | Output format: markdown, html, text, screenshot, or pageshot |
| `withIframe` | boolean | No | Include iframe content in extraction |
| `withShadowDom` | boolean | No | Extract Shadow DOM content |
| `setCookie` | string | No | Forward authentication cookies \(disables caching\) |
| `proxyUrl` | string | No | HTTP proxy URL for request routing |
| `proxy` | string | No | Country code for proxy \(e.g., "US", "UK"\) or "auto"/"none" |
| `engine` | string | No | Rendering engine: browser, direct, or cf-browser-rendering |
| `tokenBudget` | number | No | Maximum tokens for the request \(cost control\) |
| `noCache` | boolean | No | Bypass cached content for real-time retrieval |
| `cacheTolerance` | number | No | Custom cache lifetime in seconds |
| `withGeneratedAlt` | boolean | No | Generate alt text for images using VLM |
| `baseUrl` | string | No | Set to "final" to follow redirect chain |
| `locale` | string | No | Browser locale for rendering \(e.g., "en-US"\) |
| `robotsTxt` | string | No | Bot User-Agent for robots.txt checking |
| `dnt` | boolean | No | Do Not Track - prevents caching/tracking |
| `noGfm` | boolean | No | Disable GitHub Flavored Markdown |
@@ -123,11 +111,7 @@ Search the web and return top 5 results with LLM-friendly content. Each result i
| --------- | ---- | -------- | ----------- |
| `q` | string | Yes | Search query string |
| `apiKey` | string | Yes | Your Jina AI API key |
| `gl` | string | No | Two-letter country code for geo-specific results \(e.g., "US", "UK", "JP"\) |
| `location` | string | No | City-level location for localized search results |
| `hl` | string | No | Two-letter language code for results \(e.g., "en", "es", "fr"\) |
| `num` | number | No | Maximum number of results per page \(default: 5\) |
| `page` | number | No | Page number for pagination \(offset\) |
| `site` | string | No | Restrict results to specific domain\(s\). Can be comma-separated for multiple sites \(e.g., "jina.ai,github.com"\) |
| `withFavicon` | boolean | No | Include website favicons in results |
| `withImagesummary` | boolean | No | Gather all images from result pages with metadata |
@@ -137,11 +121,6 @@ Search the web and return top 5 results with LLM-friendly content. Each result i
| `withGeneratedAlt` | boolean | No | Generate alt text for images using VLM |
| `respondWith` | string | No | Set to "no-content" to get only metadata without page content |
| `returnFormat` | string | No | Output format: markdown, html, text, screenshot, or pageshot |
| `engine` | string | No | Rendering engine: browser or direct |
| `timeout` | number | No | Maximum seconds to wait for page load |
| `setCookie` | string | No | Forward authentication cookies |
| `proxyUrl` | string | No | HTTP proxy URL for request routing |
| `locale` | string | No | Browser locale for rendering \(e.g., "en-US"\) |
#### Output

View File

@@ -43,7 +43,7 @@ In Sim, the Jira integration allows your agents to seamlessly interact with your
## Usage Instructions
Integrate Jira into the workflow. Can read, write, and update issues.
Integrate Jira into the workflow. Can read, write, and update issues. Can also trigger workflows based on Jira webhook events.

View File

@@ -42,7 +42,7 @@ In Sim, the Linear integration allows your agents to seamlessly interact with yo
## Usage Instructions
Integrate Linear into the workflow. Can manage issues, comments, projects, labels, workflow states, cycles, attachments, and more.
Integrate Linear into the workflow. Can manage issues, comments, projects, labels, workflow states, cycles, attachments, and more. Can also trigger workflows based on Linear webhook events.
@@ -891,6 +891,590 @@ Mark a notification as read or unread in Linear
| --------- | ---- | ----------- |
| `notification` | object | The updated notification |
### `linear_create_customer`
Create a new customer in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Customer name |
| `domains` | array | No | Domains associated with this customer |
| `externalIds` | array | No | External IDs from other systems |
| `logoUrl` | string | No | Customer's logo URL |
| `ownerId` | string | No | ID of the user who owns this customer |
| `revenue` | number | No | Annual revenue from this customer |
| `size` | number | No | Size of the customer organization |
| `statusId` | string | No | Customer status ID |
| `tierId` | string | No | Customer tier ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The created customer |
### `linear_list_customers`
List all customers in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `first` | number | No | Number of customers to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
| `includeArchived` | boolean | No | Include archived customers \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customers` | array | Array of customers |
### `linear_create_customer_request`
Create a customer request (need) in Linear. Assign to customer, set urgency (priority: 0 = Not important, 1 = Important), and optionally link to an issue.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customerId` | string | Yes | Customer ID to assign this request to |
| `body` | string | No | Description of the customer request |
| `priority` | number | No | Urgency level: 0 = Not important, 1 = Important \(default: 0\) |
| `issueId` | string | No | Issue ID to link this request to |
| `projectId` | string | No | Project ID to link this request to |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerNeed` | object | The created customer request |
### `linear_update_customer_request`
Update a customer request (need) in Linear. Can change urgency, description, customer assignment, and linked issue.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customerNeedId` | string | Yes | Customer request ID to update |
| `body` | string | No | Updated description of the customer request |
| `priority` | number | No | Updated urgency level: 0 = Not important, 1 = Important |
| `customerId` | string | No | New customer ID to assign this request to |
| `issueId` | string | No | New issue ID to link this request to |
| `projectId` | string | No | New project ID to link this request to |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerNeed` | object | The updated customer request |
### `linear_list_customer_requests`
List all customer requests (needs) in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `first` | number | No | Number of customer requests to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
| `includeArchived` | boolean | No | Include archived customer requests \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerNeeds` | array | Array of customer requests |
### `linear_get_customer`
Get a single customer by ID in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customerId` | string | Yes | Customer ID to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The customer data |
### `linear_update_customer`
Update a customer in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customerId` | string | Yes | Customer ID to update |
| `name` | string | No | Updated customer name |
| `domains` | array | No | Updated domains |
| `externalIds` | array | No | Updated external IDs |
| `logoUrl` | string | No | Updated logo URL |
| `ownerId` | string | No | Updated owner user ID |
| `revenue` | number | No | Updated annual revenue |
| `size` | number | No | Updated organization size |
| `statusId` | string | No | Updated customer status ID |
| `tierId` | string | No | Updated customer tier ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The updated customer |
### `linear_delete_customer`
Delete a customer in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customerId` | string | Yes | Customer ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_merge_customers`
Merge two customers in Linear by moving all data from source to target
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `sourceCustomerId` | string | Yes | Source customer ID \(will be deleted after merge\) |
| `targetCustomerId` | string | Yes | Target customer ID \(will receive all data\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The merged target customer |
### `linear_create_customer_status`
Create a new customer status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Customer status name |
| `color` | string | Yes | Status color \(hex code\) |
| `displayName` | string | No | Display name for the status |
| `description` | string | No | Status description |
| `position` | number | No | Position in status list |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerStatus` | object | The created customer status |
### `linear_update_customer_status`
Update a customer status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `statusId` | string | Yes | Customer status ID to update |
| `name` | string | No | Updated status name |
| `color` | string | No | Updated status color |
| `displayName` | string | No | Updated display name |
| `description` | string | No | Updated description |
| `position` | number | No | Updated position |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerStatus` | object | The updated customer status |
### `linear_delete_customer_status`
Delete a customer status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `statusId` | string | Yes | Customer status ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_list_customer_statuses`
List all customer statuses in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerStatuses` | array | List of customer statuses |
### `linear_create_customer_tier`
Create a new customer tier in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Customer tier name |
| `color` | string | Yes | Tier color \(hex code\) |
| `displayName` | string | No | Display name for the tier |
| `description` | string | No | Tier description |
| `position` | number | No | Position in tier list |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerTier` | object | The created customer tier |
### `linear_update_customer_tier`
Update a customer tier in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tierId` | string | Yes | Customer tier ID to update |
| `name` | string | No | Updated tier name |
| `color` | string | No | Updated tier color |
| `displayName` | string | No | Updated display name |
| `description` | string | No | Updated description |
| `position` | number | No | Updated position |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerTier` | object | The updated customer tier |
### `linear_delete_customer_tier`
Delete a customer tier in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tierId` | string | Yes | Customer tier ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_list_customer_tiers`
List all customer tiers in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerTiers` | array | List of customer tiers |
### `linear_delete_project`
Delete a project in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_create_project_label`
Create a new project label in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Project label name |
| `color` | string | No | Label color \(hex code\) |
| `description` | string | No | Label description |
| `isGroup` | boolean | No | Whether this is a label group |
| `parentId` | string | No | Parent label group ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectLabel` | object | The created project label |
### `linear_update_project_label`
Update a project label in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `labelId` | string | Yes | Project label ID to update |
| `name` | string | No | Updated label name |
| `color` | string | No | Updated label color |
| `description` | string | No | Updated description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectLabel` | object | The updated project label |
### `linear_delete_project_label`
Delete a project label in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `labelId` | string | Yes | Project label ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_list_project_labels`
List all project labels in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | No | Optional project ID to filter labels for a specific project |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectLabels` | array | List of project labels |
### `linear_add_label_to_project`
Add a label to a project in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID |
| `labelId` | string | Yes | Label ID to add |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the label was added successfully |
| `projectId` | string | The project ID |
### `linear_remove_label_from_project`
Remove a label from a project in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID |
| `labelId` | string | Yes | Label ID to remove |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the label was removed successfully |
| `projectId` | string | The project ID |
### `linear_create_project_milestone`
Create a new project milestone in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID |
| `name` | string | Yes | Milestone name |
| `description` | string | No | Milestone description |
| `targetDate` | string | No | Target date \(ISO 8601\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectMilestone` | object | The created project milestone |
### `linear_update_project_milestone`
Update a project milestone in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `milestoneId` | string | Yes | Project milestone ID to update |
| `name` | string | No | Updated milestone name |
| `description` | string | No | Updated description |
| `targetDate` | string | No | Updated target date \(ISO 8601\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectMilestone` | object | The updated project milestone |
### `linear_delete_project_milestone`
Delete a project milestone in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `milestoneId` | string | Yes | Project milestone ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_list_project_milestones`
List all milestones for a project in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID to list milestones for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectMilestones` | array | List of project milestones |
### `linear_create_project_status`
Create a new project status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Project status name |
| `color` | string | Yes | Status color \(hex code\) |
| `description` | string | No | Status description |
| `indefinite` | boolean | No | Whether the status is indefinite |
| `position` | number | No | Position in status list |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectStatus` | object | The created project status |
### `linear_update_project_status`
Update a project status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `statusId` | string | Yes | Project status ID to update |
| `name` | string | No | Updated status name |
| `color` | string | No | Updated status color |
| `description` | string | No | Updated description |
| `indefinite` | boolean | No | Updated indefinite flag |
| `position` | number | No | Updated position |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectStatus` | object | The updated project status |
### `linear_delete_project_status`
Delete a project status in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `statusId` | string | Yes | Project status ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the deletion was successful |
### `linear_list_project_statuses`
List all project statuses in Linear
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectStatuses` | array | List of project statuses |
## Notes

View File

@@ -193,7 +193,10 @@ Update a task in Microsoft Planner
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the task was updated successfully |
| `message` | string | Success message when task is updated |
| `task` | object | The updated task object with all properties |
| `taskId` | string | ID of the updated task |
| `etag` | string | New ETag after update - use this for subsequent operations |
| `metadata` | object | Metadata including taskId, planId, and taskUrl |
### `microsoft_planner_delete_task`
@@ -217,21 +220,20 @@ Delete a task from Microsoft Planner
### `microsoft_planner_list_plans`
List all plans in a Microsoft 365 group
List all plans shared with the current user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupId` | string | Yes | The ID of the Microsoft 365 group |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether plans were retrieved successfully |
| `plans` | array | Array of plan objects |
| `metadata` | object | Metadata including groupId and count |
| `plans` | array | Array of plan objects shared with the current user |
| `metadata` | object | Metadata including userId and count |
### `microsoft_planner_read_plan`
@@ -361,6 +363,7 @@ Get detailed information about a task including checklist and references
| --------- | ---- | ----------- |
| `success` | boolean | Whether the task details were retrieved successfully |
| `taskDetails` | object | The task details including description, checklist, and references |
| `etag` | string | The ETag value for this task details - use this for update operations |
| `metadata` | object | Metadata including taskId |
### `microsoft_planner_update_task_details`

View File

@@ -1,6 +1,6 @@
---
title: OneDrive
description: Create, upload, and list files
description: Create, upload, download, list, and delete files
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -51,7 +51,7 @@ In Sim, the OneDrive integration enables your agents to directly interact with y
## Usage Instructions
Integrate OneDrive into the workflow. Can create text and Excel files, upload files, and list files.
Integrate OneDrive into the workflow. Can create text and Excel files, upload files, download files, list files, and delete files or folders.
@@ -136,6 +136,24 @@ List files and folders in OneDrive
| `files` | array | Array of file and folder objects with metadata |
| `nextPageToken` | string | Token for retrieving the next page of results \(optional\) |
### `onedrive_delete`
Delete a file or folder from OneDrive
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `fileId` | string | Yes | The ID of the file or folder to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the file was deleted successfully |
| `deleted` | boolean | Confirmation that the file was deleted |
| `fileId` | string | The ID of the deleted file |
## Notes

View File

@@ -165,6 +165,7 @@ Send emails using Outlook
| `to` | string | Yes | Recipient email address |
| `subject` | string | Yes | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type for the email body \(text or html\) |
| `replyToMessageId` | string | No | Message ID to reply to \(for threading\) |
| `conversationId` | string | No | Conversation ID for threading |
| `cc` | string | No | CC recipients \(comma-separated\) |
@@ -191,6 +192,7 @@ Draft emails using Outlook
| `to` | string | Yes | Recipient email address |
| `subject` | string | Yes | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type for the email body \(text or html\) |
| `cc` | string | No | CC recipients \(comma-separated\) |
| `bcc` | string | No | BCC recipients \(comma-separated\) |
| `attachments` | file[] | No | Files to attach to the email draft |

View File

@@ -128,7 +128,6 @@ Conduct comprehensive deep research across the web using Parallel AI. Synthesize
| --------- | ---- | -------- | ----------- |
| `input` | string | Yes | Research query or question \(up to 15,000 characters\) |
| `processor` | string | No | Compute level: base, lite, pro, ultra, ultra2x, ultra4x, ultra8x \(default: base\) |
| `output_schema` | string | No | Desired output format description. Use "text" for markdown reports with citations, or describe structured JSON format |
| `include_domains` | string | No | Comma-separated list of domains to restrict research to \(source policy\) |
| `exclude_domains` | string | No | Comma-separated list of domains to exclude from research \(source policy\) |
| `apiKey` | string | Yes | Parallel AI API Key |

View File

@@ -123,29 +123,6 @@ Fetch controversial posts from a subreddit
| `subreddit` | string | Name of the subreddit where posts were fetched from |
| `posts` | array | Array of controversial posts with title, author, URL, score, comments count, and metadata |
### `reddit_get_gilded`
Fetch gilded/awarded posts from a subreddit
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `subreddit` | string | Yes | The name of the subreddit to fetch posts from \(without the r/ prefix\) |
| `limit` | number | No | Maximum number of posts to return \(default: 10, max: 100\) |
| `after` | string | No | Fullname of a thing to fetch items after \(for pagination\) |
| `before` | string | No | Fullname of a thing to fetch items before \(for pagination\) |
| `count` | number | No | A count of items already seen in the listing \(used for numbering\) |
| `show` | string | No | Show items that would normally be filtered \(e.g., "all"\) |
| `sr_detail` | boolean | No | Expand subreddit details in the response |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `subreddit` | string | Name of the subreddit where posts were fetched from |
| `posts` | array | Array of gilded/awarded posts with title, author, URL, score, comments count, and metadata |
### `reddit_search`
Search for posts within a subreddit

View File

@@ -59,6 +59,7 @@ Send an email using your own Resend API key and from address
| `to` | string | Yes | Recipient email address |
| `subject` | string | Yes | Email subject |
| `body` | string | Yes | Email body content |
| `contentType` | string | No | Content type for the email body \(text or html\) |
| `resendApiKey` | string | Yes | Resend API key for sending emails |
#### Output

View File

@@ -37,13 +37,13 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
/>
<path d='M194.9 33.9001H169.8V121.4H194.9V33.9001Z' fill='white' />
<path
fill-rule='evenodd'
fillRule='evenodd'
clipRule='evenodd'
d='M142.9 41.3001L141.3 33.9001H119.7V121.4H144.7V62.1001C150.6 54.4001 160.6 55.8001 163.7 56.9001V33.9001C160.5 32.7001 148.8 30.5001 142.9 41.3001Z'
fill='white'
/>
<path
fill-rule='evenodd'
fillRule='evenodd'
clipRule='evenodd'
d='M92.8999 12.2002L68.4999 17.4002L68.3999 97.5002C68.3999 112.3 79.4999 123.2 94.2999 123.2C102.5 123.2 108.5 121.7 111.8 119.9V99.6002C108.6 100.9 92.7999 105.5 92.7999 90.7002V55.2002H111.8V33.9002H92.7999L92.8999 12.2002Z'
fill='white'

View File

@@ -325,16 +325,14 @@ Download a file from a Supabase storage bucket
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `bucket` | string | Yes | The name of the storage bucket |
| `path` | string | Yes | The path to the file to download \(e.g., "folder/file.jpg"\) |
| `fileName` | string | No | Optional filename override |
| `apiKey` | string | Yes | Your Supabase service role secret key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Operation status message |
| `fileContent` | string | File content \(base64 encoded if binary, plain text otherwise\) |
| `contentType` | string | MIME type of the file |
| `isBase64` | boolean | Whether the file content is base64 encoded |
| `file` | file | Downloaded file stored in execution files |
### `supabase_storage_list`

View File

@@ -228,7 +228,7 @@ export default function LoginPage({
},
{
onError: (ctx) => {
console.error('Login error:', ctx.error)
logger.error('Login error:', ctx.error)
const errorMessage: string[] = ['Invalid email or password']
if (ctx.error.code?.includes('EMAIL_NOT_VERIFIED')) {
@@ -288,7 +288,7 @@ export default function LoginPage({
return
}
console.error('Uncaught login error:', err)
logger.error('Uncaught login error:', err)
} finally {
setIsLoading(false)
}

View File

@@ -14,10 +14,13 @@ import {
} from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { quickValidateEmail } from '@/lib/email/validation'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { LegalLayout } from '@/app/(landing)/components'
import { soehne } from '@/app/fonts/soehne/soehne'
const logger = createLogger('CareersPage')
const validateName = (name: string): string[] => {
const errors: string[] = []
if (!name || name.trim().length < 2) {
@@ -188,7 +191,7 @@ export default function CareersPage() {
setSubmitStatus('success')
} catch (error) {
console.error('Error submitting application:', error)
logger.error('Error submitting application:', error)
setSubmitStatus('error')
} finally {
setIsSubmitting(false)

View File

@@ -325,7 +325,6 @@ export async function POST(req: NextRequest) {
...(processedFileContents.length > 0 && { fileAttachments: processedFileContents }),
}
try {
logger.info(`[${tracker.requestId}] About to call Sim Agent`, {
hasContext: agentContexts.length > 0,

View File

@@ -56,11 +56,6 @@ describe('File Upload API Route', () => {
const response = await POST(req)
const data = await response.json()
if (response.status !== 200) {
console.error('Upload failed with status:', response.status)
console.error('Error response:', data)
}
expect(response.status).toBe(200)
expect(data).toHaveProperty('url')
expect(data.url).toMatch(/\/api\/files\/serve\/.*\.txt$/)

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceAttachmentAPI')
export const dynamic = 'force-dynamic'
// Delete an attachment
@@ -45,7 +48,7 @@ export async function DELETE(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -57,7 +60,7 @@ export async function DELETE(request: Request) {
return NextResponse.json({ attachmentId, deleted: true })
} catch (error) {
console.error('Error deleting Confluence attachment:', error)
logger.error('Error deleting Confluence attachment:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceAttachmentsAPI')
export const dynamic = 'force-dynamic'
// List attachments on a page
@@ -50,7 +53,7 @@ export async function GET(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -72,7 +75,7 @@ export async function GET(request: Request) {
return NextResponse.json({ attachments })
} catch (error) {
console.error('Error listing Confluence attachments:', error)
logger.error('Error listing Confluence attachments:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceCommentAPI')
export const dynamic = 'force-dynamic'
// Update a comment
@@ -84,7 +87,7 @@ export async function PUT(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -97,7 +100,7 @@ export async function PUT(request: Request) {
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
console.error('Error updating Confluence comment:', error)
logger.error('Error updating Confluence comment:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
@@ -146,7 +149,7 @@ export async function DELETE(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -158,7 +161,7 @@ export async function DELETE(request: Request) {
return NextResponse.json({ commentId, deleted: true })
} catch (error) {
console.error('Error deleting Confluence comment:', error)
logger.error('Error deleting Confluence comment:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceCommentsAPI')
export const dynamic = 'force-dynamic'
// Create a comment
@@ -39,6 +42,8 @@ export async function POST(request: Request) {
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/footer-comments`
logger.info('Calling Confluence API', { url })
const body = {
pageId,
body: {
@@ -59,7 +64,7 @@ export async function POST(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -72,7 +77,7 @@ export async function POST(request: Request) {
const data = await response.json()
return NextResponse.json({ ...data, pageId })
} catch (error) {
console.error('Error creating Confluence comment:', error)
logger.error('Error creating Confluence comment:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
@@ -126,7 +131,7 @@ export async function GET(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -147,7 +152,7 @@ export async function GET(request: Request) {
return NextResponse.json({ comments })
} catch (error) {
console.error('Error listing Confluence comments:', error)
logger.error('Error listing Confluence comments:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceCreatePageAPI')
export const dynamic = 'force-dynamic'
export async function POST(request: Request) {
@@ -65,7 +68,7 @@ export async function POST(request: Request) {
},
}
if (parentId) {
if (parentId !== undefined && parentId !== null && parentId !== '') {
createBody.parentId = parentId
}
@@ -83,7 +86,7 @@ export async function POST(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -98,7 +101,7 @@ export async function POST(request: Request) {
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
console.error('Error creating Confluence page:', error)
logger.error('Error creating Confluence page:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,96 +0,0 @@
import { NextResponse } from 'next/server'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
export const dynamic = 'force-dynamic'
// Remove a label from a page
export async function DELETE(request: Request) {
try {
const {
domain,
accessToken,
cloudId: providedCloudId,
pageId,
labelName,
} = await request.json()
if (!domain) {
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
}
if (!accessToken) {
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
}
if (!pageId) {
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
}
if (!labelName) {
return NextResponse.json({ error: 'Label name is required' }, { status: 400 })
}
const pageIdValidation = validateAlphanumericId(pageId, 'pageId', 255)
if (!pageIdValidation.isValid) {
return NextResponse.json({ error: pageIdValidation.error }, { status: 400 })
}
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
if (!cloudIdValidation.isValid) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
// First, get all labels to find the label ID
const listUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/labels`
const listResponse = await fetch(listUrl, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
if (!listResponse.ok) {
throw new Error(`Failed to list labels: ${listResponse.status}`)
}
const listData = await listResponse.json()
const label = (listData.results || []).find((l: any) => l.name === labelName)
if (!label) {
return NextResponse.json({ error: `Label "${labelName}" not found on page` }, { status: 404 })
}
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/labels/${label.id}`
const response = await fetch(url, {
method: 'DELETE',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
})
const errorMessage =
errorData?.message || `Failed to remove Confluence label (${response.status})`
return NextResponse.json({ error: errorMessage }, { status: response.status })
}
return NextResponse.json({ pageId, labelName, removed: true })
} catch (error) {
console.error('Error removing Confluence label:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
)
}
}

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceLabelsAPI')
export const dynamic = 'force-dynamic'
// Add a label to a page
@@ -62,7 +65,7 @@ export async function POST(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -75,7 +78,7 @@ export async function POST(request: Request) {
const data = await response.json()
return NextResponse.json({ ...data, pageId, labelName })
} catch (error) {
console.error('Error adding Confluence label:', error)
logger.error('Error adding Confluence label:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
@@ -128,7 +131,7 @@ export async function GET(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -148,7 +151,7 @@ export async function GET(request: Request) {
return NextResponse.json({ labels })
} catch (error) {
console.error('Error listing Confluence labels:', error)
logger.error('Error listing Confluence labels:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluencePageAPI')
export const dynamic = 'force-dynamic'
export async function POST(request: Request) {
@@ -43,15 +46,15 @@ export async function POST(request: Request) {
})
if (!response.ok) {
console.error(`Confluence API error: ${response.status} ${response.statusText}`)
logger.error(`Confluence API error: ${response.status} ${response.statusText}`)
let errorMessage
try {
const errorData = await response.json()
console.error('Error details:', JSON.stringify(errorData, null, 2))
logger.error('Error details:', JSON.stringify(errorData, null, 2))
errorMessage = errorData.message || `Failed to fetch Confluence page (${response.status})`
} catch (e) {
console.error('Could not parse error response as JSON:', e)
logger.error('Could not parse error response as JSON:', e)
errorMessage = `Failed to fetch Confluence page: ${response.status} ${response.statusText}`
}
@@ -76,7 +79,7 @@ export async function POST(request: Request) {
},
})
} catch (error) {
console.error('Error fetching Confluence page:', error)
logger.error('Error fetching Confluence page:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
@@ -143,14 +146,27 @@ export async function PUT(request: Request) {
number: currentVersion + 1,
message: version?.message || 'Updated via API',
},
title: title,
body: {
representation: 'storage',
value: pageBody?.value || '',
},
status: 'current',
}
if (title !== undefined && title !== null && title !== '') {
updateBody.title = title
} else {
updateBody.title = currentPage.title
}
if (pageBody?.value !== undefined && pageBody?.value !== null && pageBody?.value !== '') {
updateBody.body = {
representation: 'storage',
value: pageBody.value,
}
} else {
updateBody.body = {
representation: 'storage',
value: currentPage.body?.storage?.value || '',
}
}
const response = await fetch(currentPageUrl, {
method: 'PUT',
headers: {
@@ -163,7 +179,7 @@ export async function PUT(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -178,7 +194,7 @@ export async function PUT(request: Request) {
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
console.error('Error updating Confluence page:', error)
logger.error('Error updating Confluence page:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }
@@ -226,7 +242,7 @@ export async function DELETE(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -238,7 +254,7 @@ export async function DELETE(request: Request) {
return NextResponse.json({ pageId, deleted: true })
} catch (error) {
console.error('Error deleting Confluence page:', error)
logger.error('Error deleting Confluence page:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,9 +1,12 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('Confluence Search')
export async function POST(request: Request) {
try {
const {
@@ -50,7 +53,7 @@ export async function POST(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -71,7 +74,7 @@ export async function POST(request: Request) {
return NextResponse.json({ results })
} catch (error) {
console.error('Error searching Confluence:', error)
logger.error('Error searching Confluence:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceSpaceAPI')
export const dynamic = 'force-dynamic'
// Get a specific space
@@ -49,7 +52,7 @@ export async function GET(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -62,7 +65,7 @@ export async function GET(request: Request) {
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
console.error('Error getting Confluence space:', error)
logger.error('Error getting Confluence space:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,7 +1,10 @@
import { NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateJiraCloudId } from '@/lib/security/input-validation'
import { getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluenceSpacesAPI')
export const dynamic = 'force-dynamic'
// List all spaces
@@ -40,7 +43,7 @@ export async function GET(request: Request) {
if (!response.ok) {
const errorData = await response.json().catch(() => null)
console.error('Confluence API error response:', {
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
error: JSON.stringify(errorData, null, 2),
@@ -62,7 +65,7 @@ export async function GET(request: Request) {
return NextResponse.json({ spaces })
} catch (error) {
console.error('Error listing Confluence spaces:', error)
logger.error('Error listing Confluence spaces:', error)
return NextResponse.json(
{ error: (error as Error).message || 'Internal server error' },
{ status: 500 }

View File

@@ -1,39 +0,0 @@
import { type NextRequest, NextResponse } from 'next/server'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ jobId: string }> }
) {
const { jobId } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader) {
return NextResponse.json({ error: 'Authorization header is required' }, { status: 401 })
}
try {
const response = await fetch(`https://api.firecrawl.dev/v1/crawl/${jobId}`, {
method: 'GET',
headers: {
Authorization: authHeader,
'Content-Type': 'application/json',
},
})
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ error: data.error || data.message || 'Failed to get crawl status' },
{ status: response.status }
)
}
return NextResponse.json(data)
} catch (error: any) {
return NextResponse.json(
{ error: `Failed to fetch crawl status: ${error.message}` },
{ status: 500 }
)
}
}

View File

@@ -23,6 +23,7 @@ const GmailDraftSchema = z.object({
to: z.string().min(1, 'Recipient email is required'),
subject: z.string().optional().nullable(),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
threadId: z.string().optional().nullable(),
replyToMessageId: z.string().optional().nullable(),
cc: z.string().optional().nullable(),
@@ -123,6 +124,7 @@ export async function POST(request: NextRequest) {
bcc: validatedData.bcc ?? undefined,
subject: validatedData.subject || originalSubject || '',
body: validatedData.body,
contentType: validatedData.contentType || 'text',
inReplyTo: originalMessageId,
references: originalReferences,
attachments: attachmentBuffers,
@@ -140,6 +142,7 @@ export async function POST(request: NextRequest) {
bcc: validatedData.bcc,
subject: validatedData.subject || originalSubject,
body: validatedData.body,
contentType: validatedData.contentType || 'text',
inReplyTo: originalMessageId,
references: originalReferences,
})

View File

@@ -23,6 +23,7 @@ const GmailSendSchema = z.object({
to: z.string().min(1, 'Recipient email is required'),
subject: z.string().optional().nullable(),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
threadId: z.string().optional().nullable(),
replyToMessageId: z.string().optional().nullable(),
cc: z.string().optional().nullable(),
@@ -123,6 +124,7 @@ export async function POST(request: NextRequest) {
bcc: validatedData.bcc ?? undefined,
subject: validatedData.subject || originalSubject || '',
body: validatedData.body,
contentType: validatedData.contentType || 'text',
inReplyTo: originalMessageId,
references: originalReferences,
attachments: attachmentBuffers,
@@ -140,6 +142,7 @@ export async function POST(request: NextRequest) {
bcc: validatedData.bcc,
subject: validatedData.subject || originalSubject,
body: validatedData.body,
contentType: validatedData.contentType || 'text',
inReplyTo: originalMessageId,
references: originalReferences,
})

View File

@@ -57,11 +57,11 @@ export async function PUT(request: Request) {
const summaryValue = summary || title
const fields: Record<string, any> = {}
if (summaryValue) {
if (summaryValue !== undefined && summaryValue !== null && summaryValue !== '') {
fields.summary = summaryValue
}
if (description) {
if (description !== undefined && description !== null && description !== '') {
fields.description = {
type: 'doc',
version: 1,
@@ -79,19 +79,19 @@ export async function PUT(request: Request) {
}
}
if (status) {
if (status !== undefined && status !== null && status !== '') {
fields.status = {
name: status,
}
}
if (priority) {
if (priority !== undefined && priority !== null && priority !== '') {
fields.priority = {
name: priority,
}
}
if (assignee) {
if (assignee !== undefined && assignee !== null && assignee !== '') {
fields.assignee = {
id: assignee,
}

View File

@@ -71,7 +71,7 @@ export async function POST(request: Request) {
summary: summary,
}
if (description) {
if (description !== undefined && description !== null && description !== '') {
fields.description = {
type: 'doc',
version: 1,
@@ -89,17 +89,17 @@ export async function POST(request: Request) {
}
}
if (parent) {
if (parent !== undefined && parent !== null && parent !== '') {
fields.parent = parent
}
if (priority) {
if (priority !== undefined && priority !== null && priority !== '') {
fields.priority = {
name: priority,
}
}
if (assignee) {
if (assignee !== undefined && assignee !== null && assignee !== '') {
fields.assignee = {
id: assignee,
}

View File

@@ -14,6 +14,7 @@ const MailSendSchema = z.object({
to: z.string().email('Invalid email address').min(1, 'To email is required'),
subject: z.string().min(1, 'Subject is required'),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
resendApiKey: z.string().min(1, 'Resend API key is required'),
})
@@ -50,13 +51,22 @@ export async function POST(request: NextRequest) {
const resend = new Resend(validatedData.resendApiKey)
const emailData = {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
html: validatedData.body,
text: validatedData.body.replace(/<[^>]*>/g, ''), // Strip HTML for text version
}
const contentType = validatedData.contentType || 'text'
const emailData =
contentType === 'html'
? {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
html: validatedData.body,
text: validatedData.body.replace(/<[^>]*>/g, ''), // Strip HTML for text version
}
: {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
text: validatedData.body,
}
const { data, error } = await resend.emails.send(emailData)

View File

@@ -0,0 +1,121 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('TeamsDeleteChatMessageAPI')
const TeamsDeleteChatMessageSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
chatId: z.string().min(1, 'Chat ID is required'),
messageId: z.string().min(1, 'Message ID is required'),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Teams chat delete attempt: ${authResult.error}`)
return NextResponse.json(
{
success: false,
error: authResult.error || 'Authentication required',
},
{ status: 401 }
)
}
logger.info(
`[${requestId}] Authenticated Teams chat message delete request via ${authResult.authType}`,
{
userId: authResult.userId,
}
)
const body = await request.json()
const validatedData = TeamsDeleteChatMessageSchema.parse(body)
logger.info(`[${requestId}] Deleting Teams chat message`, {
chatId: validatedData.chatId,
messageId: validatedData.messageId,
})
// First, get the current user's ID (required for chat message deletion endpoint)
const meUrl = 'https://graph.microsoft.com/v1.0/me'
const meResponse = await fetch(meUrl, {
headers: {
Authorization: `Bearer ${validatedData.accessToken}`,
},
})
if (!meResponse.ok) {
const errorData = await meResponse.json().catch(() => ({}))
logger.error(`[${requestId}] Failed to get user ID:`, errorData)
return NextResponse.json(
{
success: false,
error: errorData.error?.message || 'Failed to get user information',
},
{ status: meResponse.status }
)
}
const userData = await meResponse.json()
const userId = userData.id
logger.info(`[${requestId}] Retrieved user ID: ${userId}`)
// Now perform the softDelete operation using the correct endpoint format
const deleteUrl = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userId)}/chats/${encodeURIComponent(validatedData.chatId)}/messages/${encodeURIComponent(validatedData.messageId)}/softDelete`
const deleteResponse = await fetch(deleteUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${validatedData.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}), // softDelete requires an empty JSON body
})
if (!deleteResponse.ok) {
const errorData = await deleteResponse.json().catch(() => ({}))
logger.error(`[${requestId}] Microsoft Teams API delete error:`, errorData)
return NextResponse.json(
{
success: false,
error: errorData.error?.message || 'Failed to delete Teams message',
},
{ status: deleteResponse.status }
)
}
logger.info(`[${requestId}] Teams message deleted successfully`)
return NextResponse.json({
success: true,
output: {
deleted: true,
messageId: validatedData.messageId,
metadata: {
messageId: validatedData.messageId,
chatId: validatedData.chatId,
},
},
})
} catch (error) {
logger.error(`[${requestId}] Error deleting Teams chat message:`, error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred',
},
{ status: 500 }
)
}
}

View File

@@ -15,6 +15,7 @@ const OutlookDraftSchema = z.object({
to: z.string().min(1, 'Recipient email is required'),
subject: z.string().min(1, 'Subject is required'),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
cc: z.string().optional().nullable(),
bcc: z.string().optional().nullable(),
attachments: z.array(z.any()).optional().nullable(),
@@ -70,7 +71,7 @@ export async function POST(request: NextRequest) {
const message: any = {
subject: validatedData.subject,
body: {
contentType: 'Text',
contentType: validatedData.contentType || 'text',
content: validatedData.body,
},
toRecipients,

View File

@@ -15,6 +15,7 @@ const OutlookSendSchema = z.object({
to: z.string().min(1, 'Recipient email is required'),
subject: z.string().min(1, 'Subject is required'),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
cc: z.string().optional().nullable(),
bcc: z.string().optional().nullable(),
replyToMessageId: z.string().optional().nullable(),
@@ -72,7 +73,7 @@ export async function POST(request: NextRequest) {
const message: any = {
subject: validatedData.subject,
body: {
contentType: 'Text',
contentType: validatedData.contentType || 'text',
content: validatedData.body,
},
toRecipients,

View File

@@ -170,7 +170,7 @@ async function processSecureActions(
return { modifiedMessage, executedActions }
}
// New helper function for direct login attempt
// Helper function for direct login attempt
async function attemptDirectLogin(
stagehand: Stagehand,
variables: Record<string, string> | undefined

View File

@@ -202,7 +202,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
},
})
} catch (error) {
console.error('Error updating workspace:', error)
logger.error('Error updating workspace:', error)
return NextResponse.json({ error: 'Failed to update workspace' }, { status: 500 })
}
}

View File

@@ -14,9 +14,12 @@ import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitati
import { getSession } from '@/lib/auth'
import { sendEmail } from '@/lib/email/mailer'
import { getFromEmailAddress } from '@/lib/email/utils'
import { createLogger } from '@/lib/logs/console/logger'
import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils'
import { getBaseUrl } from '@/lib/urls/utils'
const logger = createLogger('WorkspaceInvitationAPI')
// GET /api/workspaces/invitations/[invitationId] - Get invitation details OR accept via token
export async function GET(
req: NextRequest,
@@ -163,7 +166,7 @@ export async function GET(
workspaceName: workspaceDetails.name,
})
} catch (error) {
console.error('Error fetching workspace invitation:', error)
logger.error('Error fetching workspace invitation:', error)
return NextResponse.json({ error: 'Failed to fetch invitation details' }, { status: 500 })
}
}
@@ -211,7 +214,7 @@ export async function DELETE(
return NextResponse.json({ success: true })
} catch (error) {
console.error('Error deleting workspace invitation:', error)
logger.error('Error deleting workspace invitation:', error)
return NextResponse.json({ error: 'Failed to delete invitation' }, { status: 500 })
}
}
@@ -295,7 +298,7 @@ export async function POST(
return NextResponse.json({ success: true })
} catch (error) {
console.error('Error resending workspace invitation:', error)
logger.error('Error resending workspace invitation:', error)
return NextResponse.json({ error: 'Failed to resend invitation' }, { status: 500 })
}
}

View File

@@ -3,8 +3,11 @@ import { permissions, workspace } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'
import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils'
const logger = createLogger('WorkspaceMemberAPI')
// DELETE /api/workspaces/members/[id] - Remove a member from a workspace
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id: userId } = await params
@@ -100,7 +103,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
return NextResponse.json({ success: true })
} catch (error) {
console.error('Error removing workspace member:', error)
logger.error('Error removing workspace member:', error)
return NextResponse.json({ error: 'Failed to remove workspace member' }, { status: 500 })
}
}

View File

@@ -3,8 +3,11 @@ import { permissions, type permissionTypeEnum, user } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'
import { hasAdminPermission } from '@/lib/permissions/utils'
const logger = createLogger('WorkspaceMemberAPI')
type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
// Add a member to a workspace
@@ -87,7 +90,7 @@ export async function POST(req: Request) {
message: `User added to workspace with ${permission} permission`,
})
} catch (error) {
console.error('Error adding workspace member:', error)
logger.error('Error adding workspace member:', error)
return NextResponse.json({ error: 'Failed to add workspace member' }, { status: 500 })
}
}

View File

@@ -72,7 +72,7 @@ export async function POST(req: Request) {
return NextResponse.json({ workspace: newWorkspace })
} catch (error) {
console.error('Error creating workspace:', error)
logger.error('Error creating workspace:', error)
return NextResponse.json({ error: 'Failed to create workspace' }, { status: 500 })
}
}

View File

@@ -7,6 +7,10 @@ import { AlertCircle, Paperclip, Send, Square, X } from 'lucide-react'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { VoiceInput } from '@/app/chat/components/input/voice-input'
const logger = createLogger('ChatInput')
import { createLogger } from '@/lib/logs/console/logger'
const PLACEHOLDER_MOBILE = 'Enter a message'
const PLACEHOLDER_DESKTOP = 'Enter a message or click the mic to speak'
const MAX_TEXTAREA_HEIGHT = 120 // Max height in pixels (e.g., for about 3-4 lines)
@@ -138,7 +142,7 @@ export const ChatInput: React.FC<{
reader.readAsDataURL(file)
})
} catch (error) {
console.error('Error reading file:', error)
logger.error('Error reading file:', error)
}
}

View File

@@ -4,6 +4,9 @@ import { useState } from 'react'
import { Check, Copy, LibraryBig } from 'lucide-react'
import Link from 'next/link'
import { useParams } from 'next/navigation'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('BaseOverviewComponent')
interface BaseOverviewProps {
id?: string
@@ -84,7 +87,7 @@ export function BaseOverview({
setIsCopied(true)
setTimeout(() => setIsCopied(false), 2000)
} catch (err) {
console.error('Failed to copy ID:', err)
logger.error('Failed to copy ID:', err)
}
}
}

View File

@@ -943,12 +943,10 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
setUploadError(null)
setUploadProgress({ stage: 'uploading', filesCompleted: 0, totalFiles: files.length })
// Upload files in batches with retry logic
const uploadedFiles = await uploadFilesInBatches(files)
setUploadProgress((prev) => ({ ...prev, stage: 'processing' }))
// Start async document processing
const processPayload = {
documents: uploadedFiles.map((file) => ({
...file,
@@ -999,7 +997,6 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
const processResult = await processResponse.json()
// Validate process result structure
if (!processResult.success) {
throw new ProcessingError(
`Document processing failed: ${processResult.error || 'Unknown error'}`,
@@ -1018,7 +1015,6 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
logger.info(`Successfully started processing ${uploadedFiles.length} documents`)
// Call success callback
options.onUploadComplete?.(uploadedFiles)
return uploadedFiles
@@ -1029,8 +1025,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
setUploadError(error)
options.onError?.(error)
// Show user-friendly error message in console for debugging
console.error('Document upload failed:', error.message)
logger.error('Document upload failed:', error.message)
throw err
} finally {

View File

@@ -3,6 +3,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Loader2 } from 'lucide-react'
import { useParams, useRouter, useSearchParams } from 'next/navigation'
import { createLogger } from '@/lib/logs/console/logger'
import { soehne } from '@/app/fonts/soehne/soehne'
import Controls from '@/app/workspace/[workspaceId]/logs/components/dashboard/controls'
import KPIs from '@/app/workspace/[workspaceId]/logs/components/dashboard/kpis'
@@ -14,6 +15,8 @@ import { formatCost } from '@/providers/utils'
import { useFilterStore } from '@/stores/logs/filters/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
const logger = createLogger('Dashboard')
type TimeFilter = '30m' | '1h' | '6h' | '12h' | '24h' | '3d' | '7d' | '14d' | '30d'
interface WorkflowExecution {
@@ -362,7 +365,7 @@ export default function Dashboard() {
})
setGlobalLogsMeta({ offset: mappedLogs.length, hasMore: mappedLogs.length === 50 })
} catch (err) {
console.error('Error fetching executions:', err)
logger.error('Error fetching executions:', err)
setError(err instanceof Error ? err.message : 'An error occurred')
} finally {
setLoading(false)
@@ -418,7 +421,7 @@ export default function Dashboard() {
},
}))
} catch (err) {
console.error('Error fetching workflow details:', err)
logger.error('Error fetching workflow details:', err)
}
},
[workspaceId, endTime, getStartTime, triggers]

View File

@@ -115,7 +115,7 @@ export default async function TemplatePage({ params }: TemplatePageProps) {
/>
)
} catch (error) {
console.error('Error loading template:', error)
logger.error('Error loading template:', error)
return (
<div className='flex h-screen items-center justify-center'>
<div className='text-center'>

View File

@@ -252,7 +252,7 @@ export const DiffControls = memo(function DiffControls() {
logger.error('Failed to accept changes:', error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
console.error('Workflow update failed:', errorMessage)
logger.error('Workflow update failed:', errorMessage)
alert(`Failed to save workflow changes: ${errorMessage}`)
}
}, [createCheckpoint, clearPreviewYaml, updatePreviewToolCallState, acceptChanges])

View File

@@ -27,12 +27,15 @@ import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { sanitizeForCopilot } from '@/lib/workflows/json-sanitizer'
import { formatEditSequence } from '@/lib/workflows/training/compute-edit-sequence'
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
import { useCopilotTrainingStore } from '@/stores/copilot-training/store'
const logger = createLogger('TrainingModal')
/**
* Modal for starting training sessions and viewing/exporting datasets
*/
@@ -136,7 +139,7 @@ export function TrainingModal() {
return result
} catch (error) {
console.error('Failed to send dataset to indexer:', error)
logger.error('Failed to send dataset to indexer:', error)
throw error
}
}
@@ -325,7 +328,7 @@ export function TrainingModal() {
setLiveWorkflowDescription('')
setTimeout(() => setLiveWorkflowSent(false), 5000)
} catch (error) {
console.error('Failed to send live workflow:', error)
logger.error('Failed to send live workflow:', error)
setLiveWorkflowFailed(true)
setTimeout(() => setLiveWorkflowFailed(false), 5000)
} finally {

View File

@@ -46,11 +46,21 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'https://www.googleapis.com/auth/devstorage.read_only': 'Read files from Google Cloud Storage',
'read:confluence-content.all': 'Read all Confluence content',
'read:confluence-space.summary': 'Read Confluence space information',
'read:space:confluence': 'View Confluence spaces',
'read:space-details:confluence': 'View detailed Confluence space information',
'write:confluence-content': 'Create and edit Confluence pages',
'write:confluence-space': 'Manage Confluence spaces',
'write:confluence-file': 'Upload files to Confluence',
'write:comment:confluence': 'Create and manage comments',
'write:attachment:confluence': 'Manage attachments',
'read:page:confluence': 'View Confluence pages',
'write:page:confluence': 'Create and update Confluence pages',
'read:comment:confluence': 'View comments on Confluence pages',
'write:comment:confluence': 'Create and update comments',
'delete:comment:confluence': 'Delete comments from Confluence pages',
'read:attachment:confluence': 'View attachments on Confluence pages',
'write:attachment:confluence': 'Upload and manage attachments',
'delete:attachment:confluence': 'Delete attachments from Confluence pages',
'delete:page:confluence': 'Delete Confluence pages',
'read:label:confluence': 'View labels on Confluence content',
'write:label:confluence': 'Add and remove labels',
'search:confluence': 'Search Confluence content',
'readonly:content.attachment:confluence': 'View attachments',
@@ -80,6 +90,10 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'read:jira-user': 'Read your Jira user',
'read:jira-work': 'Read your Jira work',
'write:jira-work': 'Write to your Jira work',
'manage:jira-webhook': 'Register and manage Jira webhooks',
'read:webhook:jira': 'View Jira webhooks',
'write:webhook:jira': 'Create and update Jira webhooks',
'delete:webhook:jira': 'Delete Jira webhooks',
'read:issue-event:jira': 'Read your Jira issue events',
'write:issue:jira': 'Write to your Jira issues',
'read:project:jira': 'Read your Jira projects',
@@ -94,7 +108,10 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'read:user:jira': 'Read your Jira user',
'read:field-configuration:jira': 'Read your Jira field configuration',
'read:issue-details:jira': 'Read your Jira issue details',
// New scopes for expanded Jira operations
'read:field:jira': 'Read Jira field configurations',
'read:jql:jira': 'Use JQL to filter Jira issues',
'read:comment.property:jira': 'Read Jira comment properties',
'read:issue.property:jira': 'Read Jira issue properties',
'delete:issue:jira': 'Delete Jira issues',
'write:comment:jira': 'Add and update comments on Jira issues',
'read:comment:jira': 'Read comments on Jira issues',

View File

@@ -523,7 +523,7 @@ export function ToolInput({
label: toolParams?.toolConfig?.name || toolId,
}
} catch (error) {
console.error(`Error getting tool config for ${toolId}:`, error)
logger.error(`Error getting tool config for ${toolId}:`, error)
return {
id: toolId,
label: toolId,
@@ -1012,11 +1012,13 @@ export function ToolInput({
/>
</SelectTrigger>
<SelectContent>
{uiComponent.options?.map((option: any) => (
<SelectItem key={option.id} value={option.id}>
{option.label}
</SelectItem>
))}
{uiComponent.options
?.filter((option: any) => option.id !== '')
.map((option: any) => (
<SelectItem key={option.id} value={option.id}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
@@ -1624,11 +1626,13 @@ export function ToolInput({
/>
</SelectTrigger>
<SelectContent>
{operationOptions.map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.label}
</SelectItem>
))}
{operationOptions
.filter((option) => option.id !== '')
.map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

View File

@@ -20,8 +20,6 @@ export async function applyWorkflowDiff(
content: string,
format: EditorFormat
): Promise<ApplyResult> {
console.log('🔥 applyWorkflowDiff called!', { format, contentLength: content.length })
try {
const { activeWorkflowId } = useWorkflowRegistry.getState()
@@ -41,8 +39,6 @@ export async function applyWorkflowDiff(
})
if (format === 'yaml') {
console.log('🔥 Processing YAML format!')
logger.info('Processing YAML format - calling consolidated YAML endpoint')
try {
@@ -79,9 +75,6 @@ export async function applyWorkflowDiff(
warnings: result.warnings || [],
})
// Auto layout is now handled automatically by the backend system
// when applyAutoLayout is true in the request
// Calculate applied operations (blocks + edges)
const appliedOperations = (result.data?.blocksCount || 0) + (result.data?.edgesCount || 0)

View File

@@ -3,8 +3,11 @@
import { useEffect, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { LoadingAgent } from '@/components/ui/loading-agent'
import { createLogger } from '@/lib/logs/console/logger'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
const logger = createLogger('WorkflowsPage')
export default function WorkflowsPage() {
const router = useRouter()
const { workflows, isLoading, loadWorkflows, setActiveWorkflow } = useWorkflowRegistry()
@@ -20,7 +23,7 @@ export default function WorkflowsPage() {
await loadWorkflows(workspaceId)
setHasInitialized(true)
} catch (error) {
console.error('Failed to load workflows for workspace:', error)
logger.error('Failed to load workflows for workspace:', error)
setHasInitialized(true) // Still mark as initialized to show error state
}
}

View File

@@ -72,7 +72,7 @@ export const AirtableBlock: BlockConfig<AirtableResponse> = {
title: 'Max Records',
type: 'short-input',
layout: 'half',
placeholder: 'Maximum records to return (optional)',
placeholder: 'Maximum records to return',
condition: { field: 'operation', value: 'list' },
},
{

View File

@@ -19,7 +19,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Page', id: 'read' },
{ label: 'Create Page', id: 'create' },
@@ -32,9 +31,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
{ label: 'Delete Comment', id: 'delete_comment' },
{ label: 'List Attachments', id: 'list_attachments' },
{ label: 'Delete Attachment', id: 'delete_attachment' },
{ label: 'Add Label', id: 'add_label' },
{ label: 'List Labels', id: 'list_labels' },
{ label: 'Remove Label', id: 'remove_label' },
{ label: 'Get Space', id: 'get_space' },
{ label: 'List Spaces', id: 'list_spaces' },
],
@@ -44,7 +41,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'domain',
title: 'Domain',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Confluence domain (e.g., simstudio.atlassian.net)',
required: true,
},
@@ -52,20 +48,29 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'credential',
title: 'Confluence Account',
type: 'oauth-input',
layout: 'full',
provider: 'confluence',
serviceId: 'confluence',
requiredScopes: [
'read:confluence-content.all',
'read:confluence-space.summary',
'read:space:confluence',
'read:space-details:confluence',
'write:confluence-content',
'write:confluence-space',
'write:confluence-file',
'read:content:confluence',
'read:page:confluence',
'write:page:confluence',
'read:comment:confluence',
'write:comment:confluence',
'delete:comment:confluence',
'read:attachment:confluence',
'write:attachment:confluence',
'delete:attachment:confluence',
'delete:page:confluence',
'read:label:confluence',
'write:label:confluence',
'search:confluence',
'readonly:content.attachment:confluence',
'read:me',
'offline_access',
],
@@ -76,7 +81,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'pageId',
title: 'Select Page',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'pageId',
provider: 'confluence',
serviceId: 'confluence',
@@ -88,7 +92,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'manualPageId',
title: 'Page ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'pageId',
placeholder: 'Enter Confluence page ID',
mode: 'advanced',
@@ -97,15 +100,14 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'spaceId',
title: 'Space ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Confluence space ID',
required: true,
condition: { field: 'operation', value: ['create', 'get_space'] },
},
{
id: 'title',
title: 'Title',
type: 'short-input',
layout: 'full',
placeholder: 'Enter title for the page',
condition: { field: 'operation', value: ['create', 'update'] },
},
@@ -113,7 +115,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'content',
title: 'Content',
type: 'long-input',
layout: 'full',
placeholder: 'Enter content for the page',
condition: { field: 'operation', value: ['create', 'update'] },
},
@@ -121,7 +122,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'parentId',
title: 'Parent Page ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter parent page ID (optional)',
condition: { field: 'operation', value: 'create' },
},
@@ -129,7 +129,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search query',
required: true,
condition: { field: 'operation', value: 'search' },
@@ -138,7 +137,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'comment',
title: 'Comment Text',
type: 'long-input',
layout: 'full',
placeholder: 'Enter comment text',
required: true,
condition: { field: 'operation', value: ['create_comment', 'update_comment'] },
@@ -147,7 +145,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'commentId',
title: 'Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter comment ID',
required: true,
condition: { field: 'operation', value: ['update_comment', 'delete_comment'] },
@@ -156,7 +153,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'attachmentId',
title: 'Attachment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter attachment ID',
required: true,
condition: { field: 'operation', value: 'delete_attachment' },
@@ -165,7 +161,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'labelName',
title: 'Label Name',
type: 'short-input',
layout: 'full',
placeholder: 'Enter label name',
required: true,
condition: { field: 'operation', value: ['add_label', 'remove_label'] },
@@ -174,7 +169,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: 'Enter maximum number of results (default: 25)',
condition: {
field: 'operation',
@@ -195,9 +189,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
'confluence_delete_comment',
'confluence_list_attachments',
'confluence_delete_attachment',
'confluence_add_label',
'confluence_list_labels',
'confluence_remove_label',
'confluence_get_space',
'confluence_list_spaces',
],
@@ -226,12 +218,8 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
return 'confluence_list_attachments'
case 'delete_attachment':
return 'confluence_delete_attachment'
case 'add_label':
return 'confluence_add_label'
case 'list_labels':
return 'confluence_list_labels'
case 'remove_label':
return 'confluence_remove_label'
case 'get_space':
return 'confluence_get_space'
case 'list_spaces':
@@ -253,9 +241,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
'create_comment',
'list_comments',
'list_attachments',
'add_label',
'list_labels',
'remove_label',
]
// Operations that require spaceId

View File

@@ -18,7 +18,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Send Message', id: 'discord_send_message' },
{ label: 'Get Channel Messages', id: 'discord_get_messages' },
@@ -62,7 +61,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'botToken',
title: 'Bot Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Discord bot token',
password: true,
required: true,
@@ -71,7 +69,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'serverId',
title: 'Server ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Discord server ID',
required: true,
provider: 'discord',
@@ -82,7 +79,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'channelId',
title: 'Channel ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Discord channel ID',
required: true,
provider: 'discord',
@@ -112,7 +108,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'messageId',
title: 'Message ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter message ID',
required: true,
condition: {
@@ -133,7 +128,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'content',
title: 'Message Content',
type: 'long-input',
layout: 'full',
placeholder: 'Enter message content...',
condition: {
field: 'operation',
@@ -145,7 +139,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'emoji',
title: 'Emoji',
type: 'short-input',
layout: 'full',
placeholder: 'Enter emoji (e.g., 👍 or custom:123456789)',
required: true,
condition: {
@@ -158,7 +151,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'userId',
title: 'User ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Discord user ID',
condition: {
field: 'operation',
@@ -180,7 +172,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'threadId',
title: 'Thread ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thread ID',
required: true,
condition: {
@@ -193,7 +184,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'name',
title: 'Name',
type: 'short-input',
layout: 'full',
placeholder: 'Enter name',
required: true,
condition: {
@@ -201,19 +191,27 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
value: [
'discord_create_thread',
'discord_create_channel',
'discord_update_channel',
'discord_create_role',
'discord_update_role',
'discord_create_webhook',
],
},
},
// Name (optional for updates)
{
id: 'name',
title: 'Name',
type: 'short-input',
placeholder: 'Enter new name (optional)',
condition: {
field: 'operation',
value: ['discord_update_channel', 'discord_update_role'],
},
},
// Role ID
{
id: 'roleId',
title: 'Role ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter role ID',
required: true,
condition: {
@@ -231,7 +229,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'webhookId',
title: 'Webhook ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter webhook ID',
required: true,
condition: {
@@ -244,7 +241,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'webhookToken',
title: 'Webhook Token',
type: 'short-input',
layout: 'full',
placeholder: 'Enter webhook token',
required: true,
condition: {
@@ -257,7 +253,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'inviteCode',
title: 'Invite Code',
type: 'short-input',
layout: 'full',
placeholder: 'Enter invite code',
required: true,
condition: {
@@ -270,7 +265,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'archived',
title: 'Archived',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Archive', id: 'true' },
{ label: 'Unarchive', id: 'false' },
@@ -286,7 +280,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'topic',
title: 'Topic',
type: 'long-input',
layout: 'full',
placeholder: 'Enter channel topic (optional)',
condition: {
field: 'operation',
@@ -298,7 +291,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'color',
title: 'Color',
type: 'short-input',
layout: 'full',
placeholder: 'Enter color as integer (e.g., 16711680 for red)',
condition: {
field: 'operation',
@@ -310,7 +302,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'nick',
title: 'Nickname',
type: 'short-input',
layout: 'full',
placeholder: 'Enter new nickname',
condition: {
field: 'operation',
@@ -322,7 +313,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'reason',
title: 'Reason',
type: 'short-input',
layout: 'full',
placeholder: 'Enter reason for this action',
condition: {
field: 'operation',
@@ -334,7 +324,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'limit',
title: 'Message Limit',
type: 'short-input',
layout: 'half',
placeholder: 'Number of messages (default: 10, max: 100)',
condition: { field: 'operation', value: 'discord_get_messages' },
},
@@ -343,7 +332,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'autoArchiveDuration',
title: 'Auto Archive Duration (minutes)',
type: 'dropdown',
layout: 'full',
options: [
{ label: '1 hour (60 minutes)', id: '60' },
{ label: '24 hours (1440 minutes)', id: '1440' },
@@ -361,7 +349,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'channelType',
title: 'Channel Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Text Channel', id: '0' },
{ label: 'Voice Channel', id: '2' },
@@ -380,7 +367,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'parentId',
title: 'Parent Category ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter parent category ID (optional)',
condition: {
field: 'operation',
@@ -392,7 +378,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'hoist',
title: 'Display Separately',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Yes - Display role members separately', id: 'true' },
{ label: "No - Don't display separately", id: 'false' },
@@ -408,7 +393,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'mentionable',
title: 'Mentionable',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Yes - Role can be mentioned', id: 'true' },
{ label: 'No - Role cannot be mentioned', id: 'false' },
@@ -424,7 +408,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'deleteMessageDays',
title: 'Delete Message History',
type: 'dropdown',
layout: 'full',
options: [
{ label: "Don't delete any messages", id: '0' },
{ label: 'Delete messages from last 1 day', id: '1' },
@@ -441,7 +424,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'mute',
title: 'Server Mute',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Mute member', id: 'true' },
{ label: 'Unmute member', id: 'false' },
@@ -456,7 +438,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'deaf',
title: 'Server Deafen',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Deafen member', id: 'true' },
{ label: 'Undeafen member', id: 'false' },
@@ -471,7 +452,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'maxAge',
title: 'Invite Expiration',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Never expire', id: '0' },
{ label: '30 minutes', id: '1800' },
@@ -492,7 +472,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'maxUses',
title: 'Max Uses',
type: 'short-input',
layout: 'full',
placeholder: 'Maximum number of uses (0 = unlimited)',
condition: {
field: 'operation',
@@ -504,7 +483,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'temporary',
title: 'Temporary Membership',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'No - Grant permanent membership', id: 'false' },
{ label: 'Yes - Kick on disconnect if no role', id: 'true' },
@@ -520,7 +498,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'username',
title: 'Override Username',
type: 'short-input',
layout: 'full',
placeholder: 'Custom username to display (optional)',
condition: {
field: 'operation',
@@ -532,7 +509,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'attachmentFiles',
title: 'Attachments',
type: 'file-upload',
layout: 'full',
canonicalParamId: 'files',
placeholder: 'Upload files to attach',
condition: { field: 'operation', value: 'discord_send_message' },
@@ -544,7 +520,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
id: 'files',
title: 'File Attachments',
type: 'short-input',
layout: 'full',
canonicalParamId: 'files',
placeholder: 'Reference files from previous blocks',
condition: { field: 'operation', value: 'discord_send_message' },

View File

@@ -19,7 +19,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Search', id: 'exa_search' },
{ label: 'Get Contents', id: 'exa_get_contents' },
@@ -34,7 +33,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your search query...',
condition: { field: 'operation', value: 'exa_search' },
required: true,
@@ -43,7 +41,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'numResults',
title: 'Number of Results',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'exa_search' },
},
@@ -51,14 +48,12 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'useAutoprompt',
title: 'Use Autoprompt',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'type',
title: 'Search Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Auto', id: 'auto' },
{ label: 'Neural', id: 'neural' },
@@ -72,7 +67,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'includeDomains',
title: 'Include Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'exa_search' },
},
@@ -80,47 +74,13 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'excludeDomains',
title: 'Exclude Domains',
type: 'long-input',
layout: 'full',
placeholder: 'exclude.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'startPublishedDate',
title: 'Start Published Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-01-01',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'endPublishedDate',
title: 'End Published Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-12-31',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'startCrawlDate',
title: 'Start Crawl Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-01-01',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'endCrawlDate',
title: 'End Crawl Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-12-31',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'category',
title: 'Category Filter',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'None', id: '' },
{ label: 'Company', id: 'company' },
@@ -140,28 +100,24 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'text',
title: 'Include Text',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'highlights',
title: 'Include Highlights',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'summary',
title: 'Include Summary',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_search' },
},
{
id: 'livecrawl',
title: 'Live Crawl Mode',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Never (default)', id: 'never' },
{ label: 'Fallback', id: 'fallback' },
@@ -175,7 +131,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'urls',
title: 'URLs',
type: 'long-input',
layout: 'full',
placeholder: 'Enter URLs to retrieve content from (comma-separated)...',
condition: { field: 'operation', value: 'exa_get_contents' },
required: true,
@@ -184,14 +139,12 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'text',
title: 'Include Text',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_get_contents' },
},
{
id: 'summaryQuery',
title: 'Summary Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter a query to guide the summary generation...',
condition: { field: 'operation', value: 'exa_get_contents' },
},
@@ -199,7 +152,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'subpages',
title: 'Number of Subpages',
type: 'short-input',
layout: 'full',
placeholder: '5',
condition: { field: 'operation', value: 'exa_get_contents' },
},
@@ -207,7 +159,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'subpageTarget',
title: 'Subpage Target Keywords',
type: 'long-input',
layout: 'full',
placeholder: 'docs, tutorial, about (comma-separated)',
condition: { field: 'operation', value: 'exa_get_contents' },
},
@@ -215,7 +166,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'highlights',
title: 'Include Highlights',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_get_contents' },
},
// Find Similar Links operation inputs
@@ -223,7 +173,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'url',
title: 'URL',
type: 'long-input',
layout: 'full',
placeholder: 'Enter URL to find similar links for...',
condition: { field: 'operation', value: 'exa_find_similar_links' },
required: true,
@@ -232,7 +181,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'numResults',
title: 'Number of Results',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
@@ -240,14 +188,12 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'text',
title: 'Include Text',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'includeDomains',
title: 'Include Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
@@ -255,7 +201,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'excludeDomains',
title: 'Exclude Domains',
type: 'long-input',
layout: 'full',
placeholder: 'exclude.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
@@ -263,46 +208,12 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'excludeSourceDomain',
title: 'Exclude Source Domain',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'startPublishedDate',
title: 'Start Published Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-01-01',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'endPublishedDate',
title: 'End Published Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-12-31',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'startCrawlDate',
title: 'Start Crawl Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-01-01',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'endCrawlDate',
title: 'End Crawl Date',
type: 'short-input',
layout: 'full',
placeholder: '2024-12-31',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'category',
title: 'Category Filter',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'None', id: '' },
{ label: 'Company', id: 'company' },
@@ -322,21 +233,18 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'highlights',
title: 'Include Highlights',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'summary',
title: 'Include Summary',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_find_similar_links' },
},
{
id: 'livecrawl',
title: 'Live Crawl Mode',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Never (default)', id: 'never' },
{ label: 'Fallback', id: 'fallback' },
@@ -350,7 +258,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'query',
title: 'Question',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your question...',
condition: { field: 'operation', value: 'exa_answer' },
required: true,
@@ -359,7 +266,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'text',
title: 'Include Text',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_answer' },
},
// Research operation inputs
@@ -367,7 +273,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'query',
title: 'Research Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your research topic or question...',
condition: { field: 'operation', value: 'exa_research' },
required: true,
@@ -376,7 +281,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'model',
title: 'Research Model',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Standard (default)', id: 'exa-research' },
{ label: 'Fast', id: 'exa-research-fast' },
@@ -390,7 +294,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Exa API key',
password: true,
required: true,
@@ -443,10 +346,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
type: { type: 'string', description: 'Search type' },
includeDomains: { type: 'string', description: 'Include domains filter' },
excludeDomains: { type: 'string', description: 'Exclude domains filter' },
startPublishedDate: { type: 'string', description: 'Start published date filter' },
endPublishedDate: { type: 'string', description: 'End published date filter' },
startCrawlDate: { type: 'string', description: 'Start crawl date filter' },
endCrawlDate: { type: 'string', description: 'End crawl date filter' },
category: { type: 'string', description: 'Category filter' },
text: { type: 'boolean', description: 'Include text content' },
highlights: { type: 'boolean', description: 'Include highlights' },

View File

@@ -9,7 +9,7 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
description: 'Scrape, search, crawl, map, and extract web data',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Firecrawl into the workflow. Can scrape pages, search the web, crawl entire websites, map URL structures, and extract structured data using AI.',
'Integrate Firecrawl into the workflow. Scrape pages, search the web, crawl entire sites, map URL structures, and extract structured data with AI.',
docsLink: 'https://docs.sim.ai/tools/firecrawl',
category: 'tools',
bgColor: '#181C1E',
@@ -19,7 +19,6 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Scrape', id: 'scrape' },
{ label: 'Search', id: 'search' },
@@ -33,7 +32,6 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
id: 'url',
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the website URL',
condition: {
field: 'operation',
@@ -45,44 +43,19 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
id: 'urls',
title: 'URLs',
type: 'long-input',
layout: 'full',
placeholder:
'Enter URLs as JSON array (e.g., ["https://example.com", "https://example.com/about"])',
placeholder: '["https://example.com/page1", "https://example.com/page2"]',
condition: {
field: 'operation',
value: 'extract',
},
required: true,
},
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the search query',
condition: {
field: 'operation',
value: 'search',
},
required: true,
},
{
id: 'prompt',
title: 'Prompt',
title: 'Extraction Prompt',
type: 'long-input',
layout: 'full',
placeholder: 'Natural language instruction for extraction or crawling',
condition: {
field: 'operation',
value: ['extract', 'crawl'],
},
},
{
id: 'schema',
title: 'Schema',
type: 'long-input',
layout: 'full',
placeholder: 'JSON Schema for data extraction',
placeholder:
'Describe what data to extract (e.g., "Extract product names, prices, and descriptions")',
condition: {
field: 'operation',
value: 'extract',
@@ -92,7 +65,6 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
id: 'onlyMainContent',
title: 'Only Main Content',
type: 'switch',
layout: 'half',
condition: {
field: 'operation',
value: 'scrape',
@@ -102,60 +74,16 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
id: 'formats',
title: 'Output Formats',
type: 'long-input',
layout: 'half',
placeholder: '["markdown", "html"]',
condition: {
field: 'operation',
value: 'scrape',
},
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'half',
placeholder: '100',
condition: {
field: 'operation',
value: ['crawl', 'search', 'map'],
},
},
{
id: 'timeout',
title: 'Timeout (ms)',
type: 'short-input',
layout: 'half',
placeholder: '60000',
condition: {
field: 'operation',
value: ['scrape', 'search', 'map'],
},
},
{
id: 'mobile',
title: 'Mobile Mode',
type: 'switch',
layout: 'half',
condition: {
field: 'operation',
value: 'scrape',
},
},
{
id: 'blockAds',
title: 'Block Ads',
type: 'switch',
layout: 'half',
condition: {
field: 'operation',
value: 'scrape',
},
},
{
id: 'waitFor',
title: 'Wait For (ms)',
type: 'short-input',
layout: 'half',
placeholder: '0',
condition: {
field: 'operation',
@@ -163,83 +91,49 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
},
},
{
id: 'excludePaths',
title: 'Exclude Paths',
type: 'long-input',
layout: 'full',
placeholder: '["^/admin", "^/private"]',
condition: {
field: 'operation',
value: 'crawl',
},
},
{
id: 'includePaths',
title: 'Include Paths',
type: 'long-input',
layout: 'full',
placeholder: '["^/blog", "^/docs"]',
condition: {
field: 'operation',
value: 'crawl',
},
},
{
id: 'allowSubdomains',
title: 'Allow Subdomains',
id: 'mobile',
title: 'Mobile Mode',
type: 'switch',
layout: 'half',
condition: {
field: 'operation',
value: 'crawl',
value: 'scrape',
},
},
{
id: 'allowExternalLinks',
title: 'Allow External Links',
type: 'switch',
layout: 'half',
condition: {
field: 'operation',
value: 'crawl',
},
},
{
id: 'search',
title: 'Search Filter',
id: 'timeout',
title: 'Timeout (ms)',
type: 'short-input',
layout: 'full',
placeholder: 'Filter results by relevance (e.g., "blog")',
placeholder: '60000',
condition: {
field: 'operation',
value: 'map',
value: ['scrape', 'search'],
},
},
{
id: 'includeSubdomains',
title: 'Include Subdomains',
type: 'switch',
layout: 'half',
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: {
field: 'operation',
value: ['map', 'extract'],
value: ['crawl', 'map', 'search'],
},
},
{
id: 'showSources',
title: 'Show Sources',
type: 'switch',
layout: 'half',
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Enter the search query',
condition: {
field: 'operation',
value: 'extract',
value: 'search',
},
required: true,
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Firecrawl API key',
password: true,
required: true,
@@ -271,49 +165,70 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
}
},
params: (params) => {
const { operation, limit, urls, formats, schema, ...rest } = params
const {
operation,
limit,
urls,
formats,
timeout,
waitFor,
url,
query,
onlyMainContent,
mobile,
prompt,
apiKey,
} = params
// Parse JSON fields if provided as strings
const parsedParams: Record<string, any> = { ...rest }
const result: Record<string, any> = { apiKey }
// Handle limit as number
if (limit) {
parsedParams.limit = Number.parseInt(limit)
// Handle operation-specific fields
switch (operation) {
case 'scrape':
if (url) result.url = url
if (formats) {
try {
result.formats = typeof formats === 'string' ? JSON.parse(formats) : formats
} catch {
result.formats = ['markdown']
}
}
if (timeout) result.timeout = Number.parseInt(timeout)
if (waitFor) result.waitFor = Number.parseInt(waitFor)
if (onlyMainContent != null) result.onlyMainContent = onlyMainContent
if (mobile != null) result.mobile = mobile
break
case 'search':
if (query) result.query = query
if (timeout) result.timeout = Number.parseInt(timeout)
if (limit) result.limit = Number.parseInt(limit)
break
case 'crawl':
if (url) result.url = url
if (limit) result.limit = Number.parseInt(limit)
if (onlyMainContent != null) result.onlyMainContent = onlyMainContent
break
case 'map':
if (url) result.url = url
if (limit) result.limit = Number.parseInt(limit)
break
case 'extract':
if (urls) {
try {
result.urls = typeof urls === 'string' ? JSON.parse(urls) : urls
} catch {
result.urls = [urls]
}
}
if (prompt) result.prompt = prompt
break
}
// Handle JSON array fields
if (urls && typeof urls === 'string') {
try {
parsedParams.urls = JSON.parse(urls)
} catch {
parsedParams.urls = [urls]
}
} else if (urls) {
parsedParams.urls = urls
}
if (formats && typeof formats === 'string') {
try {
parsedParams.formats = JSON.parse(formats)
} catch {
parsedParams.formats = ['markdown']
}
} else if (formats) {
parsedParams.formats = formats
}
if (schema && typeof schema === 'string') {
try {
parsedParams.schema = JSON.parse(schema)
} catch {
// Keep as string if not valid JSON
parsedParams.schema = schema
}
} else if (schema) {
parsedParams.schema = schema
}
return parsedParams
return result
},
},
},
@@ -323,40 +238,31 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
url: { type: 'string', description: 'Target website URL' },
urls: { type: 'json', description: 'Array of URLs for extraction' },
query: { type: 'string', description: 'Search query terms' },
prompt: { type: 'string', description: 'Natural language instruction' },
schema: { type: 'json', description: 'JSON Schema for extraction' },
prompt: { type: 'string', description: 'Extraction prompt' },
limit: { type: 'string', description: 'Result/page limit' },
formats: { type: 'json', description: 'Output formats array' },
onlyMainContent: { type: 'boolean', description: 'Extract only main content' },
timeout: { type: 'number', description: 'Request timeout in ms' },
waitFor: { type: 'number', description: 'Wait time before scraping in ms' },
mobile: { type: 'boolean', description: 'Use mobile emulation' },
blockAds: { type: 'boolean', description: 'Block ads and popups' },
waitFor: { type: 'number', description: 'Wait time before scraping' },
excludePaths: { type: 'json', description: 'Paths to exclude from crawl' },
includePaths: { type: 'json', description: 'Paths to include in crawl' },
allowSubdomains: { type: 'boolean', description: 'Allow subdomain crawling' },
allowExternalLinks: { type: 'boolean', description: 'Allow external links' },
search: { type: 'string', description: 'Search filter for map' },
includeSubdomains: { type: 'boolean', description: 'Include subdomains' },
showSources: { type: 'boolean', description: 'Show data sources' },
onlyMainContent: { type: 'boolean', description: 'Extract only main content' },
scrapeOptions: { type: 'json', description: 'Advanced scraping options' },
},
outputs: {
// Scrape outputs
// Scrape output
markdown: { type: 'string', description: 'Page content markdown' },
html: { type: 'string', description: 'Raw HTML content' },
metadata: { type: 'json', description: 'Page metadata' },
// Search outputs
// Search output
data: { type: 'json', description: 'Search results or extracted data' },
warning: { type: 'string', description: 'Warning messages' },
// Crawl outputs
// Crawl output
pages: { type: 'json', description: 'Crawled pages data' },
total: { type: 'number', description: 'Total pages found' },
creditsUsed: { type: 'number', description: 'Credits consumed' },
// Map outputs
// Map output
success: { type: 'boolean', description: 'Operation success status' },
links: { type: 'json', description: 'Array of discovered URLs' },
// Extract outputs
links: { type: 'json', description: 'Discovered URLs array' },
// Extract output
sources: { type: 'json', description: 'Data sources array' },
},
}

View File

@@ -84,6 +84,19 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
required: true,
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Plain Text', id: 'text' },
{ label: 'HTML', id: 'html' },
],
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
value: () => 'text',
required: false,
},
// File upload (basic mode)
{
id: 'attachmentFiles',
@@ -271,7 +284,7 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
// Source label selector (basic mode)
{
id: 'sourceLabel',
title: 'Remove From Label (Optional)',
title: 'Remove From Label',
type: 'folder-selector',
layout: 'full',
canonicalParamId: 'removeLabelIds',
@@ -290,7 +303,7 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
// Manual source label input (advanced mode)
{
id: 'manualSourceLabel',
title: 'Remove From Label (Optional)',
title: 'Remove From Label',
type: 'short-input',
layout: 'full',
canonicalParamId: 'removeLabelIds',
@@ -482,6 +495,7 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email content' },
contentType: { type: 'string', description: 'Content type (text or html)' },
threadId: { type: 'string', description: 'Thread ID to reply to (for threading)' },
replyToMessageId: {
type: 'string',

View File

@@ -84,7 +84,7 @@ export const GoogleVaultBlock: BlockConfig = {
},
{
id: 'fileName',
title: 'File Name (optional)',
title: 'File Name',
type: 'short-input',
layout: 'full',
placeholder: 'Override filename used for storage/display',

View File

@@ -18,7 +18,6 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read URL', id: 'jina_read_url' },
{ label: 'Search', id: 'jina_search' },
@@ -30,48 +29,14 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'https://example.com',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'targetSelector',
title: 'Target Selector',
type: 'short-input',
layout: 'full',
placeholder: '#main-content (CSS selector)',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'waitForSelector',
title: 'Wait For Selector',
type: 'short-input',
layout: 'full',
placeholder: '.dynamic-content (CSS selector)',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'removeSelector',
title: 'Remove Selector',
type: 'short-input',
layout: 'full',
placeholder: 'header, footer, .ad (CSS selector)',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'timeout',
title: 'Timeout (seconds)',
type: 'short-input',
layout: 'full',
placeholder: '30',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'returnFormat',
title: 'Return Format',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Markdown', id: 'markdown' },
{ label: 'HTML', id: 'html' },
@@ -86,7 +51,6 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'retainImages',
title: 'Retain Images',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'All', id: 'all' },
{ label: 'None', id: 'none' },
@@ -94,80 +58,10 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
value: () => 'all',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'engine',
title: 'Engine',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Browser', id: 'browser' },
{ label: 'Direct', id: 'direct' },
{ label: 'CF Browser Rendering', id: 'cf-browser-rendering' },
],
value: () => 'browser',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'proxy',
title: 'Proxy (Country Code)',
type: 'short-input',
layout: 'full',
placeholder: 'US, UK, auto, none',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'proxyUrl',
title: 'Proxy URL',
type: 'short-input',
layout: 'full',
placeholder: 'http://proxy.example.com:8080',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'setCookie',
title: 'Cookies',
type: 'short-input',
layout: 'full',
placeholder: 'session=abc123; token=xyz',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'tokenBudget',
title: 'Token Budget',
type: 'short-input',
layout: 'full',
placeholder: '10000',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'cacheTolerance',
title: 'Cache Tolerance (seconds)',
type: 'short-input',
layout: 'full',
placeholder: '3600',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'locale',
title: 'Locale',
type: 'short-input',
layout: 'full',
placeholder: 'en-US',
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'baseUrl',
title: 'Base URL',
type: 'dropdown',
layout: 'full',
options: [{ label: 'Final (Follow Redirects)', id: 'final' }],
condition: { field: 'operation', value: 'jina_read_url' },
},
{
id: 'readUrlOptions',
title: 'Options',
type: 'checkbox-list',
layout: 'full',
options: [
{ label: 'Use Reader LM v2 (3x cost)', id: 'useReaderLMv2' },
{ label: 'Gather Links', id: 'gatherLinks' },
@@ -187,56 +81,21 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'q',
title: 'Search Query',
type: 'long-input',
layout: 'full',
required: true,
placeholder: 'Enter your search query...',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'gl',
title: 'Country Code',
type: 'short-input',
layout: 'full',
placeholder: 'US, UK, JP, etc.',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'location',
title: 'Location',
type: 'short-input',
layout: 'full',
placeholder: 'New York, London, Tokyo',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'hl',
title: 'Language Code',
type: 'short-input',
layout: 'full',
placeholder: 'en, es, fr, etc.',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'num',
title: 'Number of Results',
type: 'short-input',
layout: 'full',
placeholder: '5',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'page',
title: 'Page Number',
type: 'short-input',
layout: 'full',
placeholder: '1',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'site',
title: 'Site Restriction',
type: 'short-input',
layout: 'full',
placeholder: 'jina.ai,github.com (comma-separated)',
condition: { field: 'operation', value: 'jina_search' },
},
@@ -244,7 +103,6 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'searchReturnFormat',
title: 'Return Format',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Markdown', id: 'markdown' },
{ label: 'HTML', id: 'html' },
@@ -257,7 +115,6 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'searchRetainImages',
title: 'Retain Images',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'All', id: 'all' },
{ label: 'None', id: 'none' },
@@ -265,55 +122,10 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
value: () => 'all',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchEngine',
title: 'Engine',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Browser', id: 'browser' },
{ label: 'Direct', id: 'direct' },
],
value: () => 'browser',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchTimeout',
title: 'Timeout (seconds)',
type: 'short-input',
layout: 'full',
placeholder: '30',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchSetCookie',
title: 'Cookies',
type: 'short-input',
layout: 'full',
placeholder: 'session=abc123; token=xyz',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchProxyUrl',
title: 'Proxy URL',
type: 'short-input',
layout: 'full',
placeholder: 'http://proxy.example.com:8080',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchLocale',
title: 'Locale',
type: 'short-input',
layout: 'full',
placeholder: 'en-US',
condition: { field: 'operation', value: 'jina_search' },
},
{
id: 'searchOptions',
title: 'Options',
type: 'checkbox-list',
layout: 'full',
options: [
{ label: 'Include Favicons', id: 'withFavicon' },
{ label: 'Gather Images', id: 'withImagesummary' },
@@ -329,7 +141,6 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter your Jina API key',
password: true,
@@ -351,46 +162,25 @@ export const JinaBlock: BlockConfig<ReadUrlResponse | SearchResponse> = {
useReaderLMv2: { type: 'boolean', description: 'Use Reader LM v2 (3x cost)' },
gatherLinks: { type: 'boolean', description: 'Gather page links' },
jsonResponse: { type: 'boolean', description: 'JSON response format' },
targetSelector: { type: 'string', description: 'CSS selector to target' },
waitForSelector: { type: 'string', description: 'CSS selector to wait for' },
removeSelector: { type: 'string', description: 'CSS selector to remove' },
timeout: { type: 'number', description: 'Timeout in seconds' },
withImagesummary: { type: 'boolean', description: 'Gather images' },
retainImages: { type: 'string', description: 'Retain images setting' },
returnFormat: { type: 'string', description: 'Output format' },
withIframe: { type: 'boolean', description: 'Include iframes' },
withShadowDom: { type: 'boolean', description: 'Include Shadow DOM' },
setCookie: { type: 'string', description: 'Authentication cookies' },
proxyUrl: { type: 'string', description: 'Proxy URL' },
proxy: { type: 'string', description: 'Proxy country code' },
engine: { type: 'string', description: 'Rendering engine' },
tokenBudget: { type: 'number', description: 'Token budget' },
noCache: { type: 'boolean', description: 'Bypass cache' },
cacheTolerance: { type: 'number', description: 'Cache tolerance seconds' },
withGeneratedAlt: { type: 'boolean', description: 'Generate image alt text' },
baseUrl: { type: 'string', description: 'Follow redirects' },
locale: { type: 'string', description: 'Browser locale' },
robotsTxt: { type: 'string', description: 'Bot User-Agent' },
dnt: { type: 'boolean', description: 'Do Not Track' },
noGfm: { type: 'boolean', description: 'Disable GitHub Flavored Markdown' },
// Search inputs
q: { type: 'string', description: 'Search query' },
gl: { type: 'string', description: 'Country code' },
location: { type: 'string', description: 'City location' },
hl: { type: 'string', description: 'Language code' },
num: { type: 'number', description: 'Number of results' },
page: { type: 'number', description: 'Page number' },
site: { type: 'string', description: 'Site restriction' },
withFavicon: { type: 'boolean', description: 'Include favicons' },
withLinksummary: { type: 'boolean', description: 'Gather links' },
respondWith: { type: 'string', description: 'Response mode' },
searchReturnFormat: { type: 'string', description: 'Search output format' },
searchRetainImages: { type: 'string', description: 'Search retain images' },
searchEngine: { type: 'string', description: 'Search engine' },
searchTimeout: { type: 'number', description: 'Search timeout' },
searchSetCookie: { type: 'string', description: 'Search cookies' },
searchProxyUrl: { type: 'string', description: 'Search proxy URL' },
searchLocale: { type: 'string', description: 'Search locale' },
},
outputs: {
// Read URL outputs

View File

@@ -2,13 +2,16 @@ import { JiraIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { JiraResponse } from '@/tools/jira/types'
import { getTrigger } from '@/triggers'
export const JiraBlock: BlockConfig<JiraResponse> = {
type: 'jira',
name: 'Jira',
description: 'Interact with Jira',
authMode: AuthMode.OAuth,
longDescription: 'Integrate Jira into the workflow. Can read, write, and update issues.',
triggerAllowed: true,
longDescription:
'Integrate Jira into the workflow. Can read, write, and update issues. Can also trigger workflows based on Jira webhook events.',
docsLink: 'https://docs.sim.ai/tools/jira',
category: 'tools',
bgColor: '#E0E0E0',
@@ -18,7 +21,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Issue', id: 'read' },
{ label: 'Update Issue', id: 'update' },
@@ -48,7 +50,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'domain',
title: 'Domain',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter Jira domain (e.g., simstudio.atlassian.net)',
},
@@ -56,7 +57,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'credential',
title: 'Jira Account',
type: 'oauth-input',
layout: 'full',
required: true,
provider: 'jira',
serviceId: 'jira',
@@ -80,7 +80,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
'read:user:jira',
'read:field-configuration:jira',
'read:issue-details:jira',
// New scopes for expanded Jira operations
'delete:issue:jira',
'write:comment:jira',
'read:comment:jira',
@@ -100,7 +99,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'projectId',
title: 'Select Project',
type: 'project-selector',
layout: 'full',
canonicalParamId: 'projectId',
provider: 'jira',
serviceId: 'jira',
@@ -113,7 +111,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'manualProjectId',
title: 'Project ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'projectId',
placeholder: 'Enter Jira project ID',
dependsOn: ['credential', 'domain'],
@@ -124,7 +121,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'issueKey',
title: 'Select Issue',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'issueKey',
provider: 'jira',
serviceId: 'jira',
@@ -158,7 +154,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'manualIssueKey',
title: 'Issue Key',
type: 'short-input',
layout: 'full',
canonicalParamId: 'issueKey',
placeholder: 'Enter Jira issue key',
dependsOn: ['credential', 'domain', 'projectId', 'manualProjectId'],
@@ -189,7 +184,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'summary',
title: 'New Summary',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter new summary for the issue',
dependsOn: ['issueKey'],
@@ -199,7 +193,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'description',
title: 'New Description',
type: 'long-input',
layout: 'full',
placeholder: 'Enter new description for the issue',
dependsOn: ['issueKey'],
condition: { field: 'operation', value: ['update', 'write'] },
@@ -209,7 +202,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'deleteSubtasks',
title: 'Delete Subtasks',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
@@ -222,7 +214,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'accountId',
title: 'Account ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter user account ID to assign',
condition: { field: 'operation', value: ['assign', 'add_watcher', 'remove_watcher'] },
@@ -232,16 +223,14 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'transitionId',
title: 'Transition ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter transition ID (e.g., 21)',
condition: { field: 'operation', value: 'transition' },
},
{
id: 'transitionComment',
title: 'Comment (Optional)',
title: 'Comment',
type: 'long-input',
layout: 'full',
placeholder: 'Add optional comment for transition',
condition: { field: 'operation', value: 'transition' },
},
@@ -250,7 +239,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'jql',
title: 'JQL Query',
type: 'long-input',
layout: 'full',
required: true,
placeholder: 'Enter JQL query (e.g., project = PROJ AND status = "In Progress")',
condition: { field: 'operation', value: 'search' },
@@ -259,7 +247,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: 'Maximum results to return (default: 50)',
condition: { field: 'operation', value: ['search', 'get_comments', 'get_worklogs'] },
},
@@ -268,7 +255,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'commentBody',
title: 'Comment Text',
type: 'long-input',
layout: 'full',
required: true,
placeholder: 'Enter comment text',
condition: { field: 'operation', value: ['add_comment', 'update_comment'] },
@@ -277,7 +263,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'commentId',
title: 'Comment ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter comment ID',
condition: { field: 'operation', value: ['update_comment', 'delete_comment'] },
@@ -287,7 +272,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'attachmentId',
title: 'Attachment ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter attachment ID',
condition: { field: 'operation', value: 'delete_attachment' },
@@ -297,7 +281,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'timeSpentSeconds',
title: 'Time Spent (seconds)',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter time in seconds (e.g., 3600 for 1 hour)',
condition: { field: 'operation', value: 'add_worklog' },
@@ -306,23 +289,20 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'timeSpentSecondsUpdate',
title: 'Time Spent (seconds) - Optional',
type: 'short-input',
layout: 'full',
placeholder: 'Enter time in seconds (leave empty to keep unchanged)',
condition: { field: 'operation', value: 'update_worklog' },
},
{
id: 'worklogComment',
title: 'Worklog Comment (Optional)',
title: 'Worklog Comment',
type: 'long-input',
layout: 'full',
placeholder: 'Enter optional worklog comment',
condition: { field: 'operation', value: ['add_worklog', 'update_worklog'] },
},
{
id: 'started',
title: 'Started At (Optional)',
title: 'Started At',
type: 'short-input',
layout: 'full',
placeholder: 'ISO timestamp (defaults to now)',
condition: { field: 'operation', value: ['add_worklog', 'update_worklog'] },
},
@@ -330,7 +310,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'worklogId',
title: 'Worklog ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter worklog ID',
condition: { field: 'operation', value: ['update_worklog', 'delete_worklog'] },
@@ -340,7 +319,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'inwardIssueKey',
title: 'Inward Issue Key',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter inward issue key (e.g., PROJ-123)',
condition: { field: 'operation', value: 'create_link' },
@@ -349,7 +327,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'outwardIssueKey',
title: 'Outward Issue Key',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter outward issue key (e.g., PROJ-456)',
condition: { field: 'operation', value: 'create_link' },
@@ -358,16 +335,14 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'linkType',
title: 'Link Type',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter link type (e.g., "Blocks", "Relates")',
condition: { field: 'operation', value: 'create_link' },
},
{
id: 'linkComment',
title: 'Link Comment (Optional)',
title: 'Link Comment',
type: 'long-input',
layout: 'full',
placeholder: 'Add optional comment for the link',
condition: { field: 'operation', value: 'create_link' },
},
@@ -375,11 +350,17 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
id: 'linkId',
title: 'Link ID',
type: 'short-input',
layout: 'full',
required: true,
placeholder: 'Enter link ID to delete',
condition: { field: 'operation', value: 'delete_link' },
},
// Trigger SubBlocks
...getTrigger('jira_issue_created').subBlocks,
...getTrigger('jira_issue_updated').subBlocks,
...getTrigger('jira_issue_deleted').subBlocks,
...getTrigger('jira_issue_commented').subBlocks,
...getTrigger('jira_worklog_created').subBlocks,
...getTrigger('jira_webhook').subBlocks,
],
tools: {
access: [
@@ -860,5 +841,39 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
// jira_bulk_read outputs
// Note: bulk_read returns an array in the output field, each item contains:
// ts, issueKey, summary, description, status, assignee, created, updated
// Trigger outputs (from webhook events)
event_type: { type: 'string', description: 'Webhook event type' },
issue_id: { type: 'string', description: 'Issue ID from webhook' },
issue_key: { type: 'string', description: 'Issue key from webhook' },
project_key: { type: 'string', description: 'Project key from webhook' },
project_name: { type: 'string', description: 'Project name from webhook' },
issue_type_name: { type: 'string', description: 'Issue type from webhook' },
priority_name: { type: 'string', description: 'Issue priority from webhook' },
status_name: { type: 'string', description: 'Issue status from webhook' },
assignee_name: { type: 'string', description: 'Assignee display name from webhook' },
assignee_email: { type: 'string', description: 'Assignee email from webhook' },
reporter_name: { type: 'string', description: 'Reporter display name from webhook' },
reporter_email: { type: 'string', description: 'Reporter email from webhook' },
comment_id: { type: 'string', description: 'Comment ID (for comment events)' },
comment_body: { type: 'string', description: 'Comment text (for comment events)' },
worklog_id: { type: 'string', description: 'Worklog ID (for worklog events)' },
time_spent: { type: 'string', description: 'Time spent (for worklog events)' },
changelog: { type: 'json', description: 'Changelog object (for update events)' },
issue: { type: 'json', description: 'Complete issue object from webhook' },
jira: { type: 'json', description: 'Complete webhook payload' },
user: { type: 'json', description: 'User object who triggered the event' },
webhook: { type: 'json', description: 'Webhook metadata' },
},
triggers: {
enabled: true,
available: [
'jira_issue_created',
'jira_issue_updated',
'jira_issue_deleted',
'jira_issue_commented',
'jira_worklog_created',
'jira_webhook',
],
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@ export const LinkupBlock: BlockConfig<LinkupSearchToolResponse> = {
id: 'q',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your search query',
required: true,
},
@@ -26,7 +25,6 @@ export const LinkupBlock: BlockConfig<LinkupSearchToolResponse> = {
id: 'outputType',
title: 'Output Type',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Answer', id: 'sourcedAnswer' },
{ label: 'Search', id: 'searchResults' },
@@ -37,7 +35,6 @@ export const LinkupBlock: BlockConfig<LinkupSearchToolResponse> = {
id: 'depth',
title: 'Search Depth',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Standard', id: 'standard' },
{ label: 'Deep', id: 'deep' },
@@ -48,53 +45,45 @@ export const LinkupBlock: BlockConfig<LinkupSearchToolResponse> = {
id: 'includeImages',
title: 'Include Images',
type: 'switch',
layout: 'half',
},
{
id: 'includeInlineCitations',
title: 'Include Inline Citations',
type: 'switch',
layout: 'half',
},
{
id: 'includeSources',
title: 'Include Sources',
type: 'switch',
layout: 'half',
},
{
id: 'fromDate',
title: 'From Date',
type: 'short-input',
layout: 'half',
placeholder: 'YYYY-MM-DD',
},
{
id: 'toDate',
title: 'To Date',
type: 'short-input',
layout: 'half',
placeholder: 'YYYY-MM-DD',
},
{
id: 'includeDomains',
title: 'Include Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
},
{
id: 'excludeDomains',
title: 'Exclude Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Linkup API key',
password: true,
required: true,

View File

@@ -41,7 +41,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Task', id: 'read_task' },
{ label: 'Create Task', id: 'create_task' },
@@ -62,7 +61,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
layout: 'full',
provider: 'microsoft-planner',
serviceId: 'microsoft-planner',
requiredScopes: [
@@ -77,23 +75,11 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
placeholder: 'Select Microsoft account',
},
// Group ID - for list_plans
{
id: 'groupId',
title: 'Group ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the Microsoft 365 group ID',
condition: { field: 'operation', value: ['list_plans'] },
dependsOn: ['credential'],
},
// Plan ID - for various operations
{
id: 'planId',
title: 'Plan ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the plan ID',
condition: {
field: 'operation',
@@ -107,7 +93,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'taskId',
title: 'Task ID',
type: 'file-selector',
layout: 'full',
placeholder: 'Select a task',
provider: 'microsoft-planner',
condition: { field: 'operation', value: ['read_task'] },
@@ -121,7 +106,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'manualTaskId',
title: 'Manual Task ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the task ID',
condition: { field: 'operation', value: ['read_task'] },
dependsOn: ['credential', 'planId'],
@@ -134,7 +118,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'taskIdForUpdate',
title: 'Task ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the task ID',
condition: {
field: 'operation',
@@ -149,7 +132,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'bucketIdForRead',
title: 'Bucket ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the bucket ID',
condition: { field: 'operation', value: ['read_bucket', 'update_bucket', 'delete_bucket'] },
dependsOn: ['credential'],
@@ -161,8 +143,8 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'etag',
title: 'ETag',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the ETag from the resource (required for updates/deletes)',
placeholder: 'Etag of the item',
required: true,
condition: {
field: 'operation',
value: [
@@ -181,7 +163,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'title',
title: 'Task Title',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the task title',
condition: { field: 'operation', value: ['create_task', 'update_task'] },
},
@@ -191,7 +172,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'name',
title: 'Bucket Name',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the bucket name',
condition: { field: 'operation', value: ['create_bucket', 'update_bucket'] },
},
@@ -201,8 +181,7 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'description',
title: 'Description',
type: 'long-input',
layout: 'full',
placeholder: 'Enter task description (optional)',
placeholder: 'Enter task description',
condition: { field: 'operation', value: ['create_task', 'update_task_details'] },
},
@@ -211,7 +190,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'dueDateTime',
title: 'Due Date',
type: 'short-input',
layout: 'full',
placeholder: 'Enter due date in ISO 8601 format (e.g., 2024-12-31T23:59:59Z)',
condition: { field: 'operation', value: ['create_task', 'update_task'] },
},
@@ -221,7 +199,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'startDateTime',
title: 'Start Date',
type: 'short-input',
layout: 'full',
placeholder: 'Enter start date in ISO 8601 format (optional)',
condition: { field: 'operation', value: ['update_task'] },
},
@@ -231,7 +208,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'assigneeUserId',
title: 'Assignee User ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the user ID to assign this task to (optional)',
condition: { field: 'operation', value: ['create_task', 'update_task'] },
},
@@ -241,7 +217,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'bucketId',
title: 'Bucket ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the bucket ID to organize the task (optional)',
condition: { field: 'operation', value: ['create_task', 'update_task'] },
},
@@ -251,7 +226,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'priority',
title: 'Priority',
type: 'short-input',
layout: 'full',
placeholder: 'Enter priority (0-10, optional)',
condition: { field: 'operation', value: ['update_task'] },
},
@@ -261,7 +235,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'percentComplete',
title: 'Percent Complete',
type: 'short-input',
layout: 'full',
placeholder: 'Enter completion percentage (0-100, optional)',
condition: { field: 'operation', value: ['update_task'] },
},
@@ -271,7 +244,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'checklist',
title: 'Checklist (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter checklist as JSON object (optional)',
condition: { field: 'operation', value: ['update_task_details'] },
},
@@ -281,7 +253,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'references',
title: 'References (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter references as JSON object (optional)',
condition: { field: 'operation', value: ['update_task_details'] },
},
@@ -291,7 +262,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'previewType',
title: 'Preview Type',
type: 'short-input',
layout: 'full',
placeholder: 'Enter preview type (automatic, noPreview, checklist, description, reference)',
condition: { field: 'operation', value: ['update_task_details'] },
},
@@ -382,13 +352,7 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
// List Plans
if (operation === 'list_plans') {
if (!groupId?.trim()) {
throw new Error('Group ID is required to list plans.')
}
return {
...baseParams,
groupId: groupId.trim(),
}
return baseParams
}
// Read Plan
@@ -476,9 +440,10 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
// Read Task
if (operation === 'read_task') {
const readParams: MicrosoftPlannerBlockParams = { ...baseParams }
const readTaskId = (taskId || manualTaskId || '').trim()
if (effectiveTaskId) {
readParams.taskId = effectiveTaskId
if (readTaskId) {
readParams.taskId = readTaskId
} else if (planId?.trim()) {
readParams.planId = planId.trim()
}
@@ -642,6 +607,10 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
previewType: { type: 'string', description: 'Preview type for task details' },
},
outputs: {
message: {
type: 'string',
description: 'Success message from the operation',
},
task: {
type: 'json',
description:
@@ -651,6 +620,14 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
type: 'json',
description: 'Array of Microsoft Planner tasks',
},
taskId: {
type: 'string',
description: 'ID of the task',
},
etag: {
type: 'string',
description: 'ETag of the resource - use this for update/delete operations',
},
plan: {
type: 'json',
description: 'The Microsoft Planner plan object',

View File

@@ -21,7 +21,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read Chat Messages', id: 'read_chat' },
{ label: 'Write Chat Message', id: 'write_chat' },
@@ -44,7 +43,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
layout: 'full',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [
@@ -76,7 +74,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'teamId',
title: 'Select Team',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'teamId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
@@ -101,7 +98,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'manualTeamId',
title: 'Team ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'teamId',
placeholder: 'Enter team ID',
mode: 'advanced',
@@ -122,7 +118,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'chatId',
title: 'Select Chat',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'chatId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
@@ -139,7 +134,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'manualChatId',
title: 'Chat ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'chatId',
placeholder: 'Enter chat ID',
mode: 'advanced',
@@ -152,7 +146,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'channelId',
title: 'Select Channel',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'channelId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
@@ -176,7 +169,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'manualChannelId',
title: 'Channel ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'channelId',
placeholder: 'Enter channel ID',
mode: 'advanced',
@@ -196,7 +188,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'messageId',
title: 'Message ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter message ID',
condition: {
field: 'operation',
@@ -217,7 +208,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'content',
title: 'Message',
type: 'long-input',
layout: 'full',
placeholder: 'Enter message content',
condition: {
field: 'operation',
@@ -235,7 +225,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'reactionType',
title: 'Reaction',
type: 'short-input',
layout: 'full',
placeholder: 'Enter emoji (e.g., ❤️, 👍, 😊)',
condition: {
field: 'operation',
@@ -248,7 +237,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'attachmentFiles',
title: 'Attachments',
type: 'file-upload',
layout: 'full',
canonicalParamId: 'files',
placeholder: 'Upload files to attach',
condition: { field: 'operation', value: ['write_chat', 'write_channel'] },
@@ -261,7 +249,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'files',
title: 'File Attachments',
type: 'short-input',
layout: 'full',
canonicalParamId: 'files',
placeholder: 'Reference files from previous blocks',
condition: { field: 'operation', value: ['write_chat', 'write_channel'] },

View File

@@ -9,10 +9,10 @@ const logger = createLogger('OneDriveBlock')
export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
type: 'onedrive',
name: 'OneDrive',
description: 'Create, upload, and list files',
description: 'Create, upload, download, list, and delete files',
authMode: AuthMode.OAuth,
longDescription:
'Integrate OneDrive into the workflow. Can create text and Excel files, upload files, and list files.',
'Integrate OneDrive into the workflow. Can create text and Excel files, upload files, download files, list files, and delete files or folders.',
docsLink: 'https://docs.sim.ai/tools/onedrive',
category: 'tools',
bgColor: '#E0E0E0',
@@ -30,6 +30,7 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
{ label: 'Upload File', id: 'upload' },
{ label: 'Download File', id: 'download' },
{ label: 'List Files', id: 'list' },
{ label: 'Delete File', id: 'delete' },
],
},
// One Drive Credentials
@@ -307,9 +308,51 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
placeholder: 'Optional: Override the filename',
condition: { field: 'operation', value: 'download' },
},
// Delete File Fields - File Selector (basic mode)
{
id: 'fileSelector',
title: 'Select File to Delete',
type: 'file-selector',
layout: 'full',
canonicalParamId: 'fileId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',
'profile',
'email',
'Files.Read',
'Files.ReadWrite',
'offline_access',
],
mimeType: 'file', // Exclude folders, show only files
placeholder: 'Select a file to delete',
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'delete' },
required: true,
},
// Manual File ID input (advanced mode)
{
id: 'manualFileId',
title: 'File ID',
type: 'short-input',
layout: 'full',
canonicalParamId: 'fileId',
placeholder: 'Enter file or folder ID to delete',
mode: 'advanced',
condition: { field: 'operation', value: 'delete' },
required: true,
},
],
tools: {
access: ['onedrive_upload', 'onedrive_create_folder', 'onedrive_download', 'onedrive_list'],
access: [
'onedrive_upload',
'onedrive_create_folder',
'onedrive_download',
'onedrive_list',
'onedrive_delete',
],
config: {
tool: (params) => {
switch (params.operation) {
@@ -322,6 +365,8 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
return 'onedrive_download'
case 'list':
return 'onedrive_list'
case 'delete':
return 'onedrive_delete'
default:
throw new Error(`Invalid OneDrive operation: ${params.operation}`)
}
@@ -365,6 +410,9 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
pageSize: { type: 'number', description: 'Results per page' },
},
outputs: {
success: { type: 'boolean', description: 'Whether the operation was successful' },
deleted: { type: 'boolean', description: 'Whether the file was deleted' },
fileId: { type: 'string', description: 'The ID of the deleted file' },
file: {
type: 'json',
description: 'The OneDrive file object, including details such as id, name, size, and more.',

View File

@@ -103,6 +103,19 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
required: true,
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Plain Text', id: 'text' },
{ label: 'HTML', id: 'html' },
],
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
value: () => 'text',
required: false,
},
// File upload (basic mode)
{
id: 'attachmentFiles',
@@ -406,6 +419,7 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email content' },
contentType: { type: 'string', description: 'Content type (Text or HTML)' },
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
attachments: { type: 'json', description: 'Files to attach (UserFile array)' },
// Forward operation inputs

View File

@@ -18,7 +18,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Search', id: 'search' },
{ label: 'Extract from URLs', id: 'extract' },
@@ -30,7 +29,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'objective',
title: 'Search Objective',
type: 'long-input',
layout: 'full',
placeholder: "When was the United Nations established? Prefer UN's websites.",
required: true,
condition: { field: 'operation', value: 'search' },
@@ -39,7 +37,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'search_queries',
title: 'Search Queries',
type: 'long-input',
layout: 'full',
placeholder:
'Enter search queries separated by commas (e.g., "Founding year UN", "Year of founding United Nations")',
required: false,
@@ -49,7 +46,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'urls',
title: 'URLs',
type: 'long-input',
layout: 'full',
placeholder:
'Enter URLs separated by commas (e.g., https://example.com, https://another.com)',
required: true,
@@ -59,7 +55,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'extract_objective',
title: 'Extract Objective',
type: 'long-input',
layout: 'full',
placeholder: 'What information to extract from the URLs?',
required: true,
condition: { field: 'operation', value: 'extract' },
@@ -68,7 +63,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'excerpts',
title: 'Include Excerpts',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Yes', id: 'true' },
{ label: 'No', id: 'false' },
@@ -80,7 +74,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'full_content',
title: 'Include Full Content',
type: 'dropdown',
layout: 'half',
options: [
{ label: 'Yes', id: 'true' },
{ label: 'No', id: 'false' },
@@ -92,26 +85,14 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'research_input',
title: 'Research Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your research question (up to 15,000 characters)',
required: true,
condition: { field: 'operation', value: 'deep_research' },
},
{
id: 'output_schema',
title: 'Output Format',
type: 'long-input',
layout: 'full',
placeholder:
'Enter "text" for markdown report, or describe desired output structure (leave empty for auto)',
required: false,
condition: { field: 'operation', value: 'deep_research' },
},
{
id: 'include_domains',
title: 'Include Domains',
type: 'short-input',
layout: 'half',
placeholder: 'Comma-separated domains to include',
required: false,
condition: { field: 'operation', value: 'deep_research' },
@@ -120,7 +101,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'exclude_domains',
title: 'Exclude Domains',
type: 'short-input',
layout: 'half',
placeholder: 'Comma-separated domains to exclude',
required: false,
condition: { field: 'operation', value: 'deep_research' },
@@ -129,7 +109,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'processor',
title: 'Processor',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Lite ($5/1K)', id: 'lite' },
{ label: 'Base ($10/1K)', id: 'base' },
@@ -147,7 +126,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'max_results',
title: 'Max Results',
type: 'short-input',
layout: 'half',
placeholder: '5',
condition: { field: 'operation', value: 'search' },
},
@@ -155,7 +133,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'max_chars_per_result',
title: 'Max Chars',
type: 'short-input',
layout: 'half',
placeholder: '1500',
condition: { field: 'operation', value: 'search' },
},
@@ -163,7 +140,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Parallel AI API key',
password: true,
required: true,
@@ -244,7 +220,6 @@ export const ParallelBlock: BlockConfig<ToolResponse> = {
excerpts: { type: 'boolean', description: 'Include excerpts' },
full_content: { type: 'boolean', description: 'Include full content' },
research_input: { type: 'string', description: 'Deep research query' },
output_schema: { type: 'string', description: 'Output format specification' },
include_domains: { type: 'string', description: 'Domains to include' },
exclude_domains: { type: 'string', description: 'Domains to exclude' },
processor: { type: 'string', description: 'Processing method' },

View File

@@ -20,12 +20,10 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Get Posts', id: 'get_posts' },
{ label: 'Get Comments', id: 'get_comments' },
{ label: 'Get Controversial Posts', id: 'get_controversial' },
{ label: 'Get Gilded Posts', id: 'get_gilded' },
{ label: 'Search Subreddit', id: 'search' },
{ label: 'Submit Post', id: 'submit_post' },
{ label: 'Vote', id: 'vote' },
@@ -44,7 +42,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'credential',
title: 'Reddit Account',
type: 'oauth-input',
layout: 'full',
provider: 'reddit',
serviceId: 'reddit',
requiredScopes: [
@@ -74,11 +71,10 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'subreddit',
title: 'Subreddit',
type: 'short-input',
layout: 'full',
placeholder: 'Enter subreddit name (without r/)',
condition: {
field: 'operation',
value: ['get_posts', 'get_comments', 'get_controversial', 'get_gilded', 'search'],
value: ['get_posts', 'get_comments', 'get_controversial', 'search'],
},
required: true,
},
@@ -88,7 +84,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'sort',
title: 'Sort By',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Hot', id: 'hot' },
{ label: 'New', id: 'new' },
@@ -105,7 +100,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'time',
title: 'Time Filter (for Top sort)',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Day', id: 'day' },
{ label: 'Week', id: 'week' },
@@ -126,7 +120,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'limit',
title: 'Max Posts',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: {
field: 'operation',
@@ -139,7 +132,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'postId',
title: 'Post ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter post ID',
condition: {
field: 'operation',
@@ -151,7 +143,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'commentSort',
title: 'Sort Comments By',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Confidence', id: 'confidence' },
{ label: 'Top', id: 'top' },
@@ -170,7 +161,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'commentLimit',
title: 'Number of Comments',
type: 'short-input',
layout: 'full',
placeholder: '50',
condition: {
field: 'operation',
@@ -183,7 +173,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'controversialTime',
title: 'Time Filter',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Hour', id: 'hour' },
{ label: 'Day', id: 'day' },
@@ -201,7 +190,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'controversialLimit',
title: 'Max Posts',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: {
field: 'operation',
@@ -209,25 +197,11 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
},
},
// Get Gilded specific fields
{
id: 'gildedLimit',
title: 'Max Posts',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: {
field: 'operation',
value: 'get_gilded',
},
},
// Search specific fields
{
id: 'searchQuery',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search query',
condition: {
field: 'operation',
@@ -239,7 +213,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'searchSort',
title: 'Sort By',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Relevance', id: 'relevance' },
{ label: 'Hot', id: 'hot' },
@@ -256,7 +229,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'searchTime',
title: 'Time Filter',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Hour', id: 'hour' },
{ label: 'Day', id: 'day' },
@@ -274,7 +246,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'searchLimit',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: {
field: 'operation',
@@ -287,7 +258,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'submitSubreddit',
title: 'Subreddit',
type: 'short-input',
layout: 'full',
placeholder: 'Enter subreddit name (without r/)',
condition: {
field: 'operation',
@@ -299,7 +269,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'title',
title: 'Post Title',
type: 'short-input',
layout: 'full',
placeholder: 'Enter post title',
condition: {
field: 'operation',
@@ -311,7 +280,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'postType',
title: 'Post Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Text Post', id: 'text' },
{ label: 'Link Post', id: 'link' },
@@ -327,7 +295,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'text',
title: 'Post Text (Markdown)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter post text in markdown format',
condition: {
field: 'operation',
@@ -342,7 +309,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'url',
title: 'URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter URL to share',
condition: {
field: 'operation',
@@ -357,7 +323,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'nsfw',
title: 'Mark as NSFW',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
@@ -372,7 +337,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'spoiler',
title: 'Mark as Spoiler',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
@@ -389,7 +353,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'voteId',
title: 'Post/Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thing ID (e.g., t3_xxxxx for post, t1_xxxxx for comment)',
condition: {
field: 'operation',
@@ -401,7 +364,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'voteDirection',
title: 'Vote Direction',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Upvote', id: '1' },
{ label: 'Unvote', id: '0' },
@@ -420,7 +382,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'saveId',
title: 'Post/Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thing ID (e.g., t3_xxxxx for post, t1_xxxxx for comment)',
condition: {
field: 'operation',
@@ -430,9 +391,8 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
},
{
id: 'saveCategory',
title: 'Category (optional)',
title: 'Category',
type: 'short-input',
layout: 'full',
placeholder: 'Enter category name',
condition: {
field: 'operation',
@@ -445,7 +405,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'replyParentId',
title: 'Parent Post/Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thing ID to reply to (e.g., t3_xxxxx for post, t1_xxxxx for comment)',
condition: {
field: 'operation',
@@ -457,7 +416,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'replyText',
title: 'Reply Text (Markdown)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter reply text in markdown format',
condition: {
field: 'operation',
@@ -471,7 +429,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'editThingId',
title: 'Post/Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thing ID to edit (e.g., t3_xxxxx for post, t1_xxxxx for comment)',
condition: {
field: 'operation',
@@ -483,7 +440,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'editText',
title: 'New Text (Markdown)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter new text in markdown format',
condition: {
field: 'operation',
@@ -497,7 +453,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'deleteId',
title: 'Post/Comment ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter thing ID to delete (e.g., t3_xxxxx for post, t1_xxxxx for comment)',
condition: {
field: 'operation',
@@ -511,7 +466,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'subscribeSubreddit',
title: 'Subreddit',
type: 'short-input',
layout: 'full',
placeholder: 'Enter subreddit name (without r/)',
condition: {
field: 'operation',
@@ -523,7 +477,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'subscribeAction',
title: 'Action',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Subscribe', id: 'sub' },
{ label: 'Unsubscribe', id: 'unsub' },
@@ -541,7 +494,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
'reddit_get_posts',
'reddit_get_comments',
'reddit_get_controversial',
'reddit_get_gilded',
'reddit_search',
'reddit_submit_post',
'reddit_vote',
@@ -564,10 +516,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
return 'reddit_get_controversial'
}
if (operation === 'get_gilded') {
return 'reddit_get_gilded'
}
if (operation === 'search') {
return 'reddit_search'
}
@@ -629,14 +577,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
}
}
if (operation === 'get_gilded') {
return {
subreddit: rest.subreddit,
limit: rest.gildedLimit ? Number.parseInt(rest.gildedLimit) : undefined,
credential: credential,
}
}
if (operation === 'search') {
return {
subreddit: rest.subreddit,
@@ -736,7 +676,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
commentLimit: { type: 'number', description: 'Maximum comments' },
controversialTime: { type: 'string', description: 'Time filter for controversial posts' },
controversialLimit: { type: 'number', description: 'Maximum controversial posts' },
gildedLimit: { type: 'number', description: 'Maximum gilded posts' },
searchQuery: { type: 'string', description: 'Search query text' },
searchSort: { type: 'string', description: 'Search result sort order' },
searchTime: { type: 'string', description: 'Time filter for search results' },

View File

@@ -45,6 +45,18 @@ export const ResendBlock: BlockConfig<MailSendResult> = {
placeholder: 'Email body content',
required: true,
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Plain Text', id: 'text' },
{ label: 'HTML', id: 'html' },
],
value: () => 'text',
required: false,
},
{
id: 'resendApiKey',
title: 'Resend API Key',
@@ -75,6 +87,7 @@ export const ResendBlock: BlockConfig<MailSendResult> = {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
contentType: { type: 'string', description: 'Content type (text or html)' },
resendApiKey: { type: 'string', description: 'Resend API key for sending emails' },
},

View File

@@ -117,7 +117,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
},
{
id: 'threadTs',
title: 'Thread Timestamp (Optional)',
title: 'Thread Timestamp',
type: 'short-input',
layout: 'full',
canonicalParamId: 'thread_ts',

View File

@@ -21,7 +21,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
// Database Operations
{ label: 'Get Many Rows', id: 'query' },
@@ -55,7 +54,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'projectId',
title: 'Project ID',
type: 'short-input',
layout: 'full',
password: true,
placeholder: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
required: true,
@@ -64,7 +62,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'table',
title: 'Table',
type: 'short-input',
layout: 'full',
placeholder: 'Name of the table',
required: true,
condition: {
@@ -76,7 +73,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'apiKey',
title: 'Service Role Secret',
type: 'short-input',
layout: 'full',
placeholder: 'Your Supabase service role secret key',
password: true,
required: true,
@@ -86,7 +82,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'data',
title: 'Data',
type: 'code',
layout: 'full',
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'insert' },
required: true,
@@ -95,7 +90,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'data',
title: 'Data',
type: 'code',
layout: 'full',
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'update' },
required: true,
@@ -104,7 +98,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'data',
title: 'Data',
type: 'code',
layout: 'full',
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'upsert' },
required: true,
@@ -114,7 +107,6 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'get_row' },
required: true,
@@ -183,7 +175,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'update' },
required: true,
@@ -252,7 +243,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'delete' },
required: true,
@@ -322,7 +312,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'status=eq.active',
condition: { field: 'operation', value: 'query' },
wandConfig: {
@@ -391,7 +380,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'orderBy',
title: 'Order By',
type: 'short-input',
layout: 'full',
placeholder: 'column_name (add DESC for descending)',
condition: { field: 'operation', value: 'query' },
},
@@ -400,7 +388,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '100',
condition: { field: 'operation', value: 'query' },
},
@@ -409,7 +396,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'functionName',
title: 'Function Name',
type: 'short-input',
layout: 'full',
placeholder: 'match_documents',
condition: { field: 'operation', value: 'vector_search' },
required: true,
@@ -418,24 +404,21 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'queryEmbedding',
title: 'Query Embedding',
type: 'code',
layout: 'full',
placeholder: '[0.1, 0.2, 0.3, ...]',
condition: { field: 'operation', value: 'vector_search' },
required: true,
},
{
id: 'matchThreshold',
title: 'Match Threshold (optional)',
title: 'Match Threshold',
type: 'short-input',
layout: 'full',
placeholder: '0.78',
condition: { field: 'operation', value: 'vector_search' },
},
{
id: 'matchCount',
title: 'Match Count (optional)',
title: 'Match Count',
type: 'short-input',
layout: 'full',
placeholder: '10',
condition: { field: 'operation', value: 'vector_search' },
},
@@ -444,7 +427,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'functionName',
title: 'Function Name',
type: 'short-input',
layout: 'full',
placeholder: 'my_function_name',
condition: { field: 'operation', value: 'rpc' },
required: true,
@@ -453,7 +435,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'params',
title: 'Parameters (JSON)',
type: 'code',
layout: 'full',
placeholder: '{\n "param1": "value1",\n "param2": "value2"\n}',
condition: { field: 'operation', value: 'rpc' },
},
@@ -462,7 +443,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'column',
title: 'Column to Search',
type: 'short-input',
layout: 'full',
placeholder: 'content',
condition: { field: 'operation', value: 'text_search' },
required: true,
@@ -471,7 +451,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'search terms',
condition: { field: 'operation', value: 'text_search' },
required: true,
@@ -480,7 +459,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'searchType',
title: 'Search Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Websearch (natural language)', id: 'websearch' },
{ label: 'Plain', id: 'plain' },
@@ -493,7 +471,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'language',
title: 'Language',
type: 'short-input',
layout: 'full',
placeholder: 'english',
condition: { field: 'operation', value: 'text_search' },
},
@@ -501,7 +478,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '100',
condition: { field: 'operation', value: 'text_search' },
},
@@ -510,15 +486,73 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'status=eq.active',
condition: { field: 'operation', value: 'count' },
wandConfig: {
enabled: true,
maintainHistory: true,
prompt: `You are an expert in PostgREST API syntax. Generate PostgREST filter expressions based on the user's request.
### CONTEXT
{context}
### CRITICAL INSTRUCTION
Return ONLY the PostgREST filter expression. Do not include any explanations, markdown formatting, or additional text. Just the raw filter expression.
### POSTGREST FILTER SYNTAX
PostgREST uses a specific syntax for filtering data. The format is:
column=operator.value
### OPERATORS
- **eq** - equals: \`id=eq.123\`
- **neq** - not equals: \`status=neq.inactive\`
- **gt** - greater than: \`age=gt.18\`
- **gte** - greater than or equal: \`score=gte.80\`
- **lt** - less than: \`price=lt.100\`
- **lte** - less than or equal: \`rating=lte.5\`
- **like** - pattern matching: \`name=like.*john*\`
- **ilike** - case-insensitive like: \`email=ilike.*@gmail.com\`
- **in** - in list: \`category=in.(tech,science,art)\`
- **is** - is null/not null: \`deleted_at=is.null\`
- **not** - negation: \`not.and=(status.eq.active,verified.eq.true)\`
### COMBINING FILTERS
- **AND**: Use \`&\` or \`and=(...)\`: \`id=eq.123&status=eq.active\`
- **OR**: Use \`or=(...)\`: \`or=(status.eq.active,status.eq.pending)\`
### EXAMPLES
**Simple equality**: "Find user with ID 123"
→ id=eq.123
**Text search**: "Find users with Gmail addresses"
→ email=ilike.*@gmail.com
**Range filter**: "Find products under $50"
→ price=lt.50
**Multiple conditions**: "Find active users over 18"
→ age=gt.18&status=eq.active
**OR condition**: "Find active or pending orders"
→ or=(status.eq.active,status.eq.pending)
**In list**: "Find posts in specific categories"
→ category=in.(tech,science,health)
**Null check**: "Find users without a profile picture"
→ profile_image=is.null
### REMEMBER
Return ONLY the PostgREST filter expression - no explanations, no markdown, no extra text.`,
placeholder: 'Describe the filter condition...',
generationType: 'postgrest',
},
},
{
id: 'countType',
title: 'Count Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Exact', id: 'exact' },
{ label: 'Planned', id: 'planned' },
@@ -532,7 +566,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'bucket',
title: 'Bucket Name',
type: 'short-input',
layout: 'full',
placeholder: 'my-bucket',
condition: {
field: 'operation',
@@ -556,7 +589,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'path',
title: 'File Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/file.jpg',
condition: { field: 'operation', value: 'storage_upload' },
required: true,
@@ -565,7 +597,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'fileContent',
title: 'File Content',
type: 'code',
layout: 'full',
placeholder: 'Base64 encoded for binary files, or plain text',
condition: { field: 'operation', value: 'storage_upload' },
required: true,
@@ -574,7 +605,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'contentType',
title: 'Content Type (MIME)',
type: 'short-input',
layout: 'full',
placeholder: 'image/jpeg',
condition: { field: 'operation', value: 'storage_upload' },
},
@@ -582,7 +612,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'upsert',
title: 'Upsert (overwrite if exists)',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
@@ -595,17 +624,22 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'path',
title: 'File Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/file.jpg',
condition: { field: 'operation', value: 'storage_download' },
required: true,
},
{
id: 'fileName',
title: 'File Name Override',
type: 'short-input',
placeholder: 'my-file.jpg',
condition: { field: 'operation', value: 'storage_download' },
},
// Storage List fields
{
id: 'path',
title: 'Folder Path (optional)',
title: 'Folder Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/',
condition: { field: 'operation', value: 'storage_list' },
},
@@ -613,7 +647,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '100',
condition: { field: 'operation', value: 'storage_list' },
},
@@ -621,7 +654,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'offset',
title: 'Offset',
type: 'short-input',
layout: 'full',
placeholder: '0',
condition: { field: 'operation', value: 'storage_list' },
},
@@ -629,7 +661,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'sortBy',
title: 'Sort By',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Name', id: 'name' },
{ label: 'Created At', id: 'created_at' },
@@ -642,7 +673,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'sortOrder',
title: 'Sort Order',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Ascending', id: 'asc' },
{ label: 'Descending', id: 'desc' },
@@ -654,7 +684,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'search',
title: 'Search',
type: 'short-input',
layout: 'full',
placeholder: 'search term',
condition: { field: 'operation', value: 'storage_list' },
},
@@ -663,7 +692,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'paths',
title: 'File Paths (JSON array)',
type: 'code',
layout: 'full',
placeholder: '["folder/file1.jpg", "folder/file2.jpg"]',
condition: { field: 'operation', value: 'storage_delete' },
required: true,
@@ -673,7 +701,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'fromPath',
title: 'From Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/old.jpg',
condition: { field: 'operation', value: 'storage_move' },
required: true,
@@ -682,7 +709,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'toPath',
title: 'To Path',
type: 'short-input',
layout: 'full',
placeholder: 'newfolder/new.jpg',
condition: { field: 'operation', value: 'storage_move' },
required: true,
@@ -692,7 +718,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'fromPath',
title: 'From Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/source.jpg',
condition: { field: 'operation', value: 'storage_copy' },
required: true,
@@ -701,7 +726,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'toPath',
title: 'To Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/copy.jpg',
condition: { field: 'operation', value: 'storage_copy' },
required: true,
@@ -711,7 +735,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'path',
title: 'File Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/file.jpg',
condition: { field: 'operation', value: 'storage_get_public_url' },
required: true,
@@ -720,7 +743,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'download',
title: 'Force Download',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
@@ -733,7 +755,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'path',
title: 'File Path',
type: 'short-input',
layout: 'full',
placeholder: 'folder/file.jpg',
condition: { field: 'operation', value: 'storage_create_signed_url' },
required: true,
@@ -742,7 +763,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'expiresIn',
title: 'Expires In (seconds)',
type: 'short-input',
layout: 'full',
placeholder: '3600',
condition: { field: 'operation', value: 'storage_create_signed_url' },
required: true,
@@ -751,7 +771,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'download',
title: 'Force Download',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False', id: 'false' },
{ label: 'True', id: 'true' },
@@ -764,7 +783,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'isPublic',
title: 'Public Bucket',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'False (Private)', id: 'false' },
{ label: 'True (Public)', id: 'true' },
@@ -776,7 +794,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'fileSizeLimit',
title: 'File Size Limit (bytes)',
type: 'short-input',
layout: 'full',
placeholder: '52428800',
condition: { field: 'operation', value: 'storage_create_bucket' },
},
@@ -784,7 +801,6 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
id: 'allowedMimeTypes',
title: 'Allowed MIME Types (JSON array)',
type: 'code',
layout: 'full',
placeholder: '["image/png", "image/jpeg"]',
condition: { field: 'operation', value: 'storage_create_bucket' },
},
@@ -1041,6 +1057,7 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
path: { type: 'string', description: 'File path in storage' },
fileContent: { type: 'string', description: 'File content (base64 for binary)' },
contentType: { type: 'string', description: 'MIME type of the file' },
fileName: { type: 'string', description: 'Optional filename override for downloaded file' },
upsert: { type: 'boolean', description: 'Whether to overwrite existing file' },
download: { type: 'boolean', description: 'Whether to force download' },
paths: { type: 'array', description: 'Array of file paths' },
@@ -1068,17 +1085,9 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
type: 'number',
description: 'Row count for count operations',
},
fileContent: {
type: 'string',
description: 'Downloaded file content (base64 for binary files)',
},
contentType: {
type: 'string',
description: 'MIME type of downloaded file',
},
isBase64: {
type: 'boolean',
description: 'Whether file content is base64 encoded',
file: {
type: 'files',
description: 'Downloaded file stored in execution files',
},
publicUrl: {
type: 'string',

View File

@@ -19,7 +19,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Search', id: 'tavily_search' },
{ label: 'Extract Content', id: 'tavily_extract' },
@@ -32,7 +31,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'query',
title: 'Search Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your search query...',
condition: { field: 'operation', value: 'tavily_search' },
required: true,
@@ -41,7 +39,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'max_results',
title: 'Max Results',
type: 'short-input',
layout: 'full',
placeholder: '5',
condition: { field: 'operation', value: 'tavily_search' },
},
@@ -49,7 +46,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'topic',
title: 'Topic',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'General', id: 'general' },
{ label: 'News', id: 'news' },
@@ -62,7 +58,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'search_depth',
title: 'Search Depth',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Basic', id: 'basic' },
{ label: 'Advanced', id: 'advanced' },
@@ -74,57 +69,48 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'include_answer',
title: 'Include Answer',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'None', id: 'false' },
{ label: 'None', id: '' },
{ label: 'Basic', id: 'basic' },
{ label: 'Advanced', id: 'advanced' },
],
value: () => 'false',
value: () => '',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'include_raw_content',
title: 'Include Raw Content',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'None', id: 'false' },
{ label: 'None', id: '' },
{ label: 'Markdown', id: 'markdown' },
{ label: 'Text', id: 'text' },
],
value: () => 'false',
value: () => '',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'include_images',
title: 'Include Images',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'include_image_descriptions',
title: 'Include Image Descriptions',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'include_favicon',
title: 'Include Favicon',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_search' },
},
{
id: 'time_range',
title: 'Time Range',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'All Time', id: '' },
{ label: 'Day', id: 'd' },
@@ -139,7 +125,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'include_domains',
title: 'Include Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'tavily_search' },
},
@@ -147,7 +132,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'exclude_domains',
title: 'Exclude Domains',
type: 'long-input',
layout: 'full',
placeholder: 'example.com, another.com (comma-separated)',
condition: { field: 'operation', value: 'tavily_search' },
},
@@ -155,7 +139,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'country',
title: 'Country',
type: 'short-input',
layout: 'full',
placeholder: 'US',
condition: { field: 'operation', value: 'tavily_search' },
},
@@ -163,7 +146,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'urls',
title: 'URL',
type: 'long-input',
layout: 'full',
placeholder: 'Enter URL to extract content from...',
condition: { field: 'operation', value: 'tavily_extract' },
required: true,
@@ -172,7 +154,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'extract_depth',
title: 'Extract Depth',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Basic', id: 'basic' },
{ label: 'Advanced', id: 'advanced' },
@@ -184,7 +165,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'format',
title: 'Format',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Markdown', id: 'markdown' },
{ label: 'Text', id: 'text' },
@@ -196,23 +176,18 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'include_images',
title: 'Include Images',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_extract' },
},
{
id: 'include_favicon',
title: 'Include Favicon',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_extract' },
},
{
id: 'url',
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'https://example.com',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
required: true,
@@ -221,7 +196,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'instructions',
title: 'Instructions',
type: 'long-input',
layout: 'full',
placeholder: 'Natural language directions for the crawler...',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -229,7 +203,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'max_depth',
title: 'Max Depth',
type: 'short-input',
layout: 'full',
placeholder: '1',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -237,7 +210,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'max_breadth',
title: 'Max Breadth',
type: 'short-input',
layout: 'full',
placeholder: '20',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -245,7 +217,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '50',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -253,7 +224,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'select_paths',
title: 'Select Paths',
type: 'long-input',
layout: 'full',
placeholder: '/docs/.*, /api/.* (regex patterns, comma-separated)',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -261,7 +231,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'select_domains',
title: 'Select Domains',
type: 'long-input',
layout: 'full',
placeholder: '^docs\\.example\\.com$ (regex patterns, comma-separated)',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -269,7 +238,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'exclude_paths',
title: 'Exclude Paths',
type: 'long-input',
layout: 'full',
placeholder: '/private/.*, /admin/.* (regex patterns, comma-separated)',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -277,7 +245,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'exclude_domains',
title: 'Exclude Domains',
type: 'long-input',
layout: 'full',
placeholder: '^private\\.example\\.com$ (regex patterns, comma-separated)',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
@@ -285,23 +252,18 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'allow_external',
title: 'Allow External Links',
type: 'switch',
layout: 'full',
value: () => 'true',
condition: { field: 'operation', value: ['tavily_crawl', 'tavily_map'] },
},
{
id: 'include_images',
title: 'Include Images',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_crawl' },
},
{
id: 'extract_depth',
title: 'Extract Depth',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Basic', id: 'basic' },
{ label: 'Advanced', id: 'advanced' },
@@ -313,7 +275,6 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'format',
title: 'Format',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Markdown', id: 'markdown' },
{ label: 'Text', id: 'text' },
@@ -325,15 +286,12 @@ export const TavilyBlock: BlockConfig<TavilyResponse> = {
id: 'include_favicon',
title: 'Include Favicon',
type: 'switch',
layout: 'full',
value: () => 'false',
condition: { field: 'operation', value: 'tavily_crawl' },
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
layout: 'full',
placeholder: 'Enter your Tavily API key',
password: true,
required: true,

View File

@@ -44,7 +44,7 @@ export const WorkflowBlock: BlockConfig = {
},
{
id: 'input',
title: 'Input Variable (Optional)',
title: 'Input Variable',
type: 'short-input',
placeholder: 'Select a variable to pass to the child workflow',
description: 'This variable will be available as start.input in the child workflow',

View File

@@ -1798,13 +1798,13 @@ export function StripeIcon(props: SVGProps<SVGSVGElement>) {
/>
<path d='M194.9 33.9001H169.8V121.4H194.9V33.9001Z' fill='white' />
<path
fill-rule='evenodd'
fillRule='evenodd'
clipRule='evenodd'
d='M142.9 41.3001L141.3 33.9001H119.7V121.4H144.7V62.1001C150.6 54.4001 160.6 55.8001 163.7 56.9001V33.9001C160.5 32.7001 148.8 30.5001 142.9 41.3001Z'
fill='white'
/>
<path
fill-rule='evenodd'
fillRule='evenodd'
clipRule='evenodd'
d='M92.8999 12.2002L68.4999 17.4002L68.3999 97.5002C68.3999 112.3 79.4999 123.2 94.2999 123.2C102.5 123.2 108.5 121.7 111.8 119.9V99.6002C108.6 100.9 92.7999 105.5 92.7999 90.7002V55.2002H111.8V33.9002H92.7999L92.8999 12.2002Z'
fill='white'

View File

@@ -781,14 +781,24 @@ export const auth = betterAuth({
scopes: [
'read:confluence-content.all',
'read:confluence-space.summary',
'read:space:confluence',
'read:space-details:confluence',
'write:confluence-content',
'write:confluence-space',
'write:confluence-file',
'read:page:confluence',
'write:page:confluence',
'read:comment:confluence',
'read:content:confluence',
'write:comment:confluence',
'delete:comment:confluence',
'read:attachment:confluence',
'write:attachment:confluence',
'delete:attachment:confluence',
'delete:page:confluence',
'read:label:confluence',
'write:label:confluence',
'search:confluence',
'readonly:content.attachment:confluence',
'read:me',
'offline_access',
],
@@ -913,7 +923,6 @@ export const auth = betterAuth({
'read:field-configuration:jira',
'read:issue-details:jira',
'read:issue-event:jira',
// New scopes for expanded Jira operations
'delete:issue:jira',
'write:comment:jira',
'read:comment:jira',

View File

@@ -1,5 +1,8 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { Chunk, StructuredDataOptions } from './types'
const logger = createLogger('StructuredDataChunker')
// Configuration for structured data chunking (CSV, XLSX, etc.)
const STRUCTURED_CHUNKING_CONFIG = {
// Target 2000-3000 tokens per chunk for better semantic meaning
@@ -46,7 +49,7 @@ export class StructuredDataChunker {
const optimalRowsPerChunk =
StructuredDataChunker.calculateOptimalRowsPerChunk(estimatedTokensPerRow)
console.log(
logger.info(
`Structured data chunking: ${lines.length} rows, ~${estimatedTokensPerRow} tokens/row, ${optimalRowsPerChunk} rows/chunk`
)
@@ -99,7 +102,7 @@ export class StructuredDataChunker {
chunks.push(StructuredDataChunker.createChunk(chunkContent, chunkStartRow, lines.length - 1))
}
console.log(`Created ${chunks.length} chunks from ${lines.length} rows of structured data`)
logger.info(`Created ${chunks.length} chunks from ${lines.length} rows of structured data`)
return chunks
}

View File

@@ -1,6 +1,6 @@
import { z } from 'zod'
// Tool IDs supported by the new Copilot runtime
// Tool IDs supported by the Copilot runtime
export const ToolIds = z.enum([
'get_user_workflow',
'edit_workflow',
@@ -21,13 +21,10 @@ export const ToolIds = z.enum([
'list_gdrive_files',
'read_gdrive_file',
'reason',
// New tools
'list_user_workflows',
'get_workflow_from_name',
// New variable tools
'get_global_workflow_variables',
'set_global_workflow_variables',
// New
'oauth_request_access',
'get_trigger_blocks',
])

View File

@@ -359,14 +359,26 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
scopes: [
'read:confluence-content.all',
'read:confluence-space.summary',
'read:space:confluence',
'read:space-details:confluence',
'write:confluence-content',
'write:confluence-space',
'write:confluence-file',
'read:page:confluence',
'write:page:confluence',
'read:comment:confluence',
'write:comment:confluence',
'delete:comment:confluence',
'delete:attachment:confluence',
'read:content:confluence',
'delete:page:confluence',
'write:label:confluence',
'read:label:confluence',
'read:attachment:confluence',
'write:attachment:confluence',
'read:label:confluence',
'write:label:confluence',
'search:confluence',
'readonly:content.attachment:confluence',
'read:me',
'offline_access',
],

View File

@@ -384,6 +384,93 @@ export async function verifyProviderAuth(
}
}
if (foundWebhook.provider === 'linear') {
const secret = providerConfig.secret as string | undefined
if (secret) {
const signature = request.headers.get('Linear-Signature')
if (!signature) {
logger.warn(`[${requestId}] Linear webhook missing signature header`)
return new NextResponse('Unauthorized - Missing Linear signature', { status: 401 })
}
const { validateLinearSignature } = await import('@/lib/webhooks/utils.server')
const isValidSignature = validateLinearSignature(secret, signature, rawBody)
if (!isValidSignature) {
logger.warn(`[${requestId}] Linear signature verification failed`, {
signatureLength: signature.length,
secretLength: secret.length,
})
return new NextResponse('Unauthorized - Invalid Linear signature', { status: 401 })
}
logger.debug(`[${requestId}] Linear signature verified successfully`)
}
}
if (foundWebhook.provider === 'jira') {
const secret = providerConfig.secret as string | undefined
if (secret) {
const signature = request.headers.get('X-Hub-Signature')
if (!signature) {
logger.warn(`[${requestId}] Jira webhook missing signature header`)
return new NextResponse('Unauthorized - Missing Jira signature', { status: 401 })
}
const { validateJiraSignature } = await import('@/lib/webhooks/utils.server')
const isValidSignature = validateJiraSignature(secret, signature, rawBody)
if (!isValidSignature) {
logger.warn(`[${requestId}] Jira signature verification failed`, {
signatureLength: signature.length,
secretLength: secret.length,
})
return new NextResponse('Unauthorized - Invalid Jira signature', { status: 401 })
}
logger.debug(`[${requestId}] Jira signature verified successfully`)
}
}
if (foundWebhook.provider === 'github') {
const secret = providerConfig.secret as string | undefined
if (secret) {
// GitHub supports both SHA-256 (preferred) and SHA-1 (legacy)
const signature256 = request.headers.get('X-Hub-Signature-256')
const signature1 = request.headers.get('X-Hub-Signature')
const signature = signature256 || signature1
if (!signature) {
logger.warn(`[${requestId}] GitHub webhook missing signature header`)
return new NextResponse('Unauthorized - Missing GitHub signature', { status: 401 })
}
const { validateGitHubSignature } = await import('@/lib/webhooks/utils.server')
const isValidSignature = validateGitHubSignature(secret, signature, rawBody)
if (!isValidSignature) {
logger.warn(`[${requestId}] GitHub signature verification failed`, {
signatureLength: signature.length,
secretLength: secret.length,
usingSha256: !!signature256,
})
return new NextResponse('Unauthorized - Invalid GitHub signature', { status: 401 })
}
logger.debug(`[${requestId}] GitHub signature verified successfully`, {
usingSha256: !!signature256,
})
}
}
if (foundWebhook.provider === 'generic') {
if (providerConfig.requireAuth) {
const configToken = providerConfig.token
@@ -621,6 +708,35 @@ export async function queueWebhookExecution(
}
}
// Jira event filtering for event-specific triggers
if (foundWebhook.provider === 'jira') {
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const triggerId = providerConfig.triggerId as string | undefined
if (triggerId && triggerId !== 'jira_webhook') {
const webhookEvent = body.webhookEvent as string | undefined
const { isJiraEventMatch } = await import('@/triggers/jira/utils')
if (!isJiraEventMatch(triggerId, webhookEvent || '', body)) {
logger.debug(
`[${options.requestId}] Jira event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`,
{
webhookId: foundWebhook.id,
workflowId: foundWorkflow.id,
triggerId,
receivedEvent: webhookEvent,
}
)
// Return 200 OK to prevent Jira from retrying
return NextResponse.json({
message: 'Event type does not match trigger configuration. Ignoring.',
})
}
}
}
const headers = Object.fromEntries(request.headers.entries())
// For Microsoft Teams Graph notifications, extract unique identifiers for idempotency

View File

@@ -1233,6 +1233,70 @@ export async function formatWebhookInput(
}
}
if (foundWebhook.provider === 'linear') {
// Linear webhook payload structure:
// { action, type, webhookId, webhookTimestamp, organizationId, createdAt, actor, data, updatedFrom? }
return {
// Extract top-level fields from Linear payload
action: body.action || '',
type: body.type || '',
webhookId: body.webhookId || '',
webhookTimestamp: body.webhookTimestamp || 0,
organizationId: body.organizationId || '',
createdAt: body.createdAt || '',
actor: body.actor || null,
data: body.data || null,
updatedFrom: body.updatedFrom || null,
// Keep webhook metadata
webhook: {
data: {
provider: 'linear',
path: foundWebhook.path,
providerConfig: foundWebhook.providerConfig,
payload: body,
headers: Object.fromEntries(request.headers.entries()),
method: request.method,
},
},
workflowId: foundWorkflow.id,
}
}
// Jira webhook format
if (foundWebhook.provider === 'jira') {
const { extractIssueData, extractCommentData, extractWorklogData } = await import(
'@/triggers/jira/utils'
)
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const triggerId = providerConfig.triggerId as string | undefined
let extractedData
if (triggerId === 'jira_issue_commented') {
extractedData = extractCommentData(body)
} else if (triggerId === 'jira_worklog_created') {
extractedData = extractWorklogData(body)
} else {
extractedData = extractIssueData(body)
}
return {
...extractedData,
webhook: {
data: {
provider: 'jira',
path: foundWebhook.path,
providerConfig: foundWebhook.providerConfig,
payload: body,
headers: Object.fromEntries(request.headers.entries()),
method: request.method,
},
},
workflowId: foundWorkflow.id,
}
}
if (foundWebhook.provider === 'stripe') {
return {
...body,
@@ -1279,25 +1343,21 @@ export function validateMicrosoftTeamsSignature(
body: string
): boolean {
try {
// Basic validation first
if (!hmacSecret || !signature || !body) {
return false
}
// Check if signature has correct format
if (!signature.startsWith('HMAC ')) {
return false
}
const providedSignature = signature.substring(5) // Remove 'HMAC ' prefix
const providedSignature = signature.substring(5)
// Compute HMAC SHA256 signature using Node.js crypto
const crypto = require('crypto')
const secretBytes = Buffer.from(hmacSecret, 'base64')
const bodyBytes = Buffer.from(body, 'utf8')
const computedHash = crypto.createHmac('sha256', secretBytes).update(bodyBytes).digest('base64')
// Constant-time comparison to prevent timing attacks
if (computedHash.length !== providedSignature.length) {
return false
}
@@ -1356,6 +1416,167 @@ export function validateTypeformSignature(
}
}
/**
* Validates a Linear webhook request signature using HMAC SHA-256
* @param secret - Linear webhook secret (plain text)
* @param signature - Linear-Signature header value (hex-encoded HMAC SHA-256 signature)
* @param body - Raw request body string
* @returns Whether the signature is valid
*/
export function validateLinearSignature(secret: string, signature: string, body: string): boolean {
try {
if (!secret || !signature || !body) {
logger.warn('Linear signature validation missing required fields', {
hasSecret: !!secret,
hasSignature: !!signature,
hasBody: !!body,
})
return false
}
const crypto = require('crypto')
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
logger.debug('Linear signature comparison', {
computedSignature: `${computedHash.substring(0, 10)}...`,
providedSignature: `${signature.substring(0, 10)}...`,
computedLength: computedHash.length,
providedLength: signature.length,
match: computedHash === signature,
})
if (computedHash.length !== signature.length) {
return false
}
let result = 0
for (let i = 0; i < computedHash.length; i++) {
result |= computedHash.charCodeAt(i) ^ signature.charCodeAt(i)
}
return result === 0
} catch (error) {
logger.error('Error validating Linear signature:', error)
return false
}
}
/**
* Validates a Jira webhook request signature using HMAC SHA-256
* @param secret - Jira webhook secret (plain text)
* @param signature - X-Hub-Signature header value (format: 'sha256=<hex>')
* @param body - Raw request body string
* @returns Whether the signature is valid
*/
export function validateJiraSignature(secret: string, signature: string, body: string): boolean {
try {
if (!secret || !signature || !body) {
logger.warn('Jira signature validation missing required fields', {
hasSecret: !!secret,
hasSignature: !!signature,
hasBody: !!body,
})
return false
}
if (!signature.startsWith('sha256=')) {
logger.warn('Jira signature has invalid format (expected sha256=)', {
signaturePrefix: signature.substring(0, 10),
})
return false
}
const providedSignature = signature.substring(7)
const crypto = require('crypto')
const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex')
logger.debug('Jira signature comparison', {
computedSignature: `${computedHash.substring(0, 10)}...`,
providedSignature: `${providedSignature.substring(0, 10)}...`,
computedLength: computedHash.length,
providedLength: providedSignature.length,
match: computedHash === providedSignature,
})
if (computedHash.length !== providedSignature.length) {
return false
}
let result = 0
for (let i = 0; i < computedHash.length; i++) {
result |= computedHash.charCodeAt(i) ^ providedSignature.charCodeAt(i)
}
return result === 0
} catch (error) {
logger.error('Error validating Jira signature:', error)
return false
}
}
/**
* Validates a GitHub webhook request signature using HMAC SHA-256 or SHA-1
* @param secret - GitHub webhook secret (plain text)
* @param signature - X-Hub-Signature-256 or X-Hub-Signature header value (format: 'sha256=<hex>' or 'sha1=<hex>')
* @param body - Raw request body string
* @returns Whether the signature is valid
*/
export function validateGitHubSignature(secret: string, signature: string, body: string): boolean {
try {
if (!secret || !signature || !body) {
logger.warn('GitHub signature validation missing required fields', {
hasSecret: !!secret,
hasSignature: !!signature,
hasBody: !!body,
})
return false
}
const crypto = require('crypto')
let algorithm: 'sha256' | 'sha1'
let providedSignature: string
if (signature.startsWith('sha256=')) {
algorithm = 'sha256'
providedSignature = signature.substring(7)
} else if (signature.startsWith('sha1=')) {
algorithm = 'sha1'
providedSignature = signature.substring(5)
} else {
logger.warn('GitHub signature has invalid format', {
signature: `${signature.substring(0, 10)}...`,
})
return false
}
const computedHash = crypto.createHmac(algorithm, secret).update(body, 'utf8').digest('hex')
logger.debug('GitHub signature comparison', {
algorithm,
computedSignature: `${computedHash.substring(0, 10)}...`,
providedSignature: `${providedSignature.substring(0, 10)}...`,
computedLength: computedHash.length,
providedLength: providedSignature.length,
match: computedHash === providedSignature,
})
if (computedHash.length !== providedSignature.length) {
return false
}
let result = 0
for (let i = 0; i < computedHash.length; i++) {
result |= computedHash.charCodeAt(i) ^ providedSignature.charCodeAt(i)
}
return result === 0
} catch (error) {
logger.error('Error validating GitHub signature:', error)
return false
}
}
/**
* Process webhook provider-specific verification
*/

View File

@@ -1,109 +0,0 @@
import type { ToolConfig } from '@/tools/types'
export interface ConfluenceAddLabelParams {
accessToken: string
domain: string
pageId: string
labelName: string
cloudId?: string
}
export interface ConfluenceAddLabelResponse {
success: boolean
output: {
ts: string
pageId: string
labelName: string
added: boolean
}
}
export const confluenceAddLabelTool: ToolConfig<
ConfluenceAddLabelParams,
ConfluenceAddLabelResponse
> = {
id: 'confluence_add_label',
name: 'Confluence Add Label',
description: 'Add a label to a Confluence page.',
version: '1.0.0',
oauth: {
required: true,
provider: 'confluence',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Confluence',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
},
pageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Confluence page ID to add label to',
},
labelName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label name to add',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
},
},
request: {
url: () => '/api/tools/confluence/labels',
method: 'POST',
headers: (params: ConfluenceAddLabelParams) => {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params: ConfluenceAddLabelParams) => {
return {
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
pageId: params.pageId,
labelName: params.labelName,
}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
ts: new Date().toISOString(),
pageId: data.pageId || '',
labelName: data.labelName || '',
added: true,
},
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of operation' },
pageId: { type: 'string', description: 'Page ID' },
labelName: { type: 'string', description: 'Label name' },
added: { type: 'boolean', description: 'Addition status' },
},
}

View File

@@ -63,7 +63,17 @@ export const confluenceGetSpaceTool: ToolConfig<
},
request: {
url: () => '/api/tools/confluence/space',
url: (params: ConfluenceGetSpaceParams) => {
const query = new URLSearchParams({
domain: params.domain,
accessToken: params.accessToken,
spaceId: params.spaceId,
})
if (params.cloudId) {
query.set('cloudId', params.cloudId)
}
return `/api/tools/confluence/space?${query.toString()}`
},
method: 'GET',
headers: (params: ConfluenceGetSpaceParams) => {
return {
@@ -71,14 +81,6 @@ export const confluenceGetSpaceTool: ToolConfig<
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params: ConfluenceGetSpaceParams) => {
return {
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
spaceId: params.spaceId,
}
},
},
transformResponse: async (response: Response) => {

View File

@@ -1,51 +1,31 @@
// Page operations
// Label operations
import { confluenceAddLabelTool } from '@/tools/confluence/add_label'
// Comment operations
import { confluenceCreateCommentTool } from '@/tools/confluence/create_comment'
import { confluenceCreatePageTool } from '@/tools/confluence/create_page'
import { confluenceDeleteAttachmentTool } from '@/tools/confluence/delete_attachment'
import { confluenceDeleteCommentTool } from '@/tools/confluence/delete_comment'
import { confluenceDeletePageTool } from '@/tools/confluence/delete_page'
// Space operations
import { confluenceGetSpaceTool } from '@/tools/confluence/get_space'
// Attachment operations
import { confluenceListAttachmentsTool } from '@/tools/confluence/list_attachments'
import { confluenceListCommentsTool } from '@/tools/confluence/list_comments'
import { confluenceListLabelsTool } from '@/tools/confluence/list_labels'
import { confluenceListSpacesTool } from '@/tools/confluence/list_spaces'
import { confluenceRemoveLabelTool } from '@/tools/confluence/remove_label'
import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
// Search operations
import { confluenceSearchTool } from '@/tools/confluence/search'
import { confluenceUpdateTool } from '@/tools/confluence/update'
import { confluenceUpdateCommentTool } from '@/tools/confluence/update_comment'
// Page operations exports
export { confluenceRetrieveTool }
export { confluenceUpdateTool }
export { confluenceCreatePageTool }
export { confluenceDeletePageTool }
// Search operations exports
export { confluenceSearchTool }
// Comment operations exports
export { confluenceCreateCommentTool }
export { confluenceListCommentsTool }
export { confluenceUpdateCommentTool }
export { confluenceDeleteCommentTool }
// Attachment operations exports
export { confluenceListAttachmentsTool }
export { confluenceDeleteAttachmentTool }
// Label operations exports
export { confluenceAddLabelTool }
export { confluenceListLabelsTool }
export { confluenceRemoveLabelTool }
// Space operations exports
export { confluenceGetSpaceTool }
export { confluenceListSpacesTool }
export {
confluenceRetrieveTool,
confluenceUpdateTool,
confluenceCreatePageTool,
confluenceDeletePageTool,
confluenceSearchTool,
confluenceCreateCommentTool,
confluenceListCommentsTool,
confluenceUpdateCommentTool,
confluenceDeleteCommentTool,
confluenceListAttachmentsTool,
confluenceDeleteAttachmentTool,
confluenceListLabelsTool,
confluenceGetSpaceTool,
confluenceListSpacesTool,
}

View File

@@ -71,7 +71,18 @@ export const confluenceListAttachmentsTool: ToolConfig<
},
request: {
url: () => '/api/tools/confluence/attachments',
url: (params: ConfluenceListAttachmentsParams) => {
const query = new URLSearchParams({
domain: params.domain,
accessToken: params.accessToken,
pageId: params.pageId,
limit: String(params.limit || 25),
})
if (params.cloudId) {
query.set('cloudId', params.cloudId)
}
return `/api/tools/confluence/attachments?${query.toString()}`
},
method: 'GET',
headers: (params: ConfluenceListAttachmentsParams) => {
return {

View File

@@ -70,7 +70,18 @@ export const confluenceListCommentsTool: ToolConfig<
},
request: {
url: () => '/api/tools/confluence/comments',
url: (params: ConfluenceListCommentsParams) => {
const query = new URLSearchParams({
domain: params.domain,
accessToken: params.accessToken,
pageId: params.pageId,
limit: String(params.limit || 25),
})
if (params.cloudId) {
query.set('cloudId', params.cloudId)
}
return `/api/tools/confluence/comments?${query.toString()}`
},
method: 'GET',
headers: (params: ConfluenceListCommentsParams) => {
return {

View File

@@ -62,7 +62,17 @@ export const confluenceListLabelsTool: ToolConfig<
},
request: {
url: () => '/api/tools/confluence/labels',
url: (params: ConfluenceListLabelsParams) => {
const query = new URLSearchParams({
domain: params.domain,
accessToken: params.accessToken,
pageId: params.pageId,
})
if (params.cloudId) {
query.set('cloudId', params.cloudId)
}
return `/api/tools/confluence/labels?${query.toString()}`
},
method: 'GET',
headers: (params: ConfluenceListLabelsParams) => {
return {
@@ -70,14 +80,6 @@ export const confluenceListLabelsTool: ToolConfig<
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params: ConfluenceListLabelsParams) => {
return {
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
pageId: params.pageId,
}
},
},
transformResponse: async (response: Response) => {

View File

@@ -64,7 +64,17 @@ export const confluenceListSpacesTool: ToolConfig<
},
request: {
url: () => '/api/tools/confluence/spaces',
url: (params: ConfluenceListSpacesParams) => {
const query = new URLSearchParams({
domain: params.domain,
accessToken: params.accessToken,
limit: String(params.limit || 25),
})
if (params.cloudId) {
query.set('cloudId', params.cloudId)
}
return `/api/tools/confluence/spaces?${query.toString()}`
},
method: 'GET',
headers: (params: ConfluenceListSpacesParams) => {
return {

View File

@@ -1,108 +0,0 @@
import type { ToolConfig } from '@/tools/types'
export interface ConfluenceRemoveLabelParams {
accessToken: string
domain: string
pageId: string
labelName: string
cloudId?: string
}
export interface ConfluenceRemoveLabelResponse {
success: boolean
output: {
ts: string
pageId: string
labelName: string
removed: boolean
}
}
export const confluenceRemoveLabelTool: ToolConfig<
ConfluenceRemoveLabelParams,
ConfluenceRemoveLabelResponse
> = {
id: 'confluence_remove_label',
name: 'Confluence Remove Label',
description: 'Remove a label from a Confluence page.',
version: '1.0.0',
oauth: {
required: true,
provider: 'confluence',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Confluence',
},
domain: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
},
pageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Confluence page ID to remove label from',
},
labelName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label name to remove',
},
cloudId: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
},
},
request: {
url: () => '/api/tools/confluence/label',
method: 'DELETE',
headers: (params: ConfluenceRemoveLabelParams) => {
return {
Accept: 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params: ConfluenceRemoveLabelParams) => {
return {
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
pageId: params.pageId,
labelName: params.labelName,
}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
ts: new Date().toISOString(),
pageId: data.pageId || '',
labelName: data.labelName || '',
removed: true,
},
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of operation' },
pageId: { type: 'string', description: 'Page ID' },
labelName: { type: 'string', description: 'Label name' },
removed: { type: 'boolean', description: 'Removal status' },
},
}

View File

@@ -53,9 +53,11 @@ export const discordEditMessageTool: ToolConfig<
Authorization: `Bot ${params.botToken}`,
}),
body: (params: DiscordEditMessageParams) => {
return {
content: params.content,
const body: any = {}
if (params.content !== undefined && params.content !== null && params.content !== '') {
body.content = params.content
}
return body
},
},

View File

@@ -1,25 +1,17 @@
// Existing tools
import { discordAddReactionTool } from '@/tools/discord/add_reaction'
import { discordArchiveThreadTool } from '@/tools/discord/archive_thread'
import { discordAssignRoleTool } from '@/tools/discord/assign_role'
import { discordBanMemberTool } from '@/tools/discord/ban_member'
// Channel operations
import { discordCreateChannelTool } from '@/tools/discord/create_channel'
// Invite operations
import { discordCreateInviteTool } from '@/tools/discord/create_invite'
// Role operations
import { discordCreateRoleTool } from '@/tools/discord/create_role'
// Thread operations
import { discordCreateThreadTool } from '@/tools/discord/create_thread'
// Webhook operations
import { discordCreateWebhookTool } from '@/tools/discord/create_webhook'
import { discordDeleteChannelTool } from '@/tools/discord/delete_channel'
import { discordDeleteInviteTool } from '@/tools/discord/delete_invite'
import { discordDeleteMessageTool } from '@/tools/discord/delete_message'
import { discordDeleteRoleTool } from '@/tools/discord/delete_role'
import { discordDeleteWebhookTool } from '@/tools/discord/delete_webhook'
// Message operations
import { discordEditMessageTool } from '@/tools/discord/edit_message'
import { discordExecuteWebhookTool } from '@/tools/discord/execute_webhook'
import { discordGetChannelTool } from '@/tools/discord/get_channel'
@@ -30,7 +22,6 @@ import { discordGetServerTool } from '@/tools/discord/get_server'
import { discordGetUserTool } from '@/tools/discord/get_user'
import { discordGetWebhookTool } from '@/tools/discord/get_webhook'
import { discordJoinThreadTool } from '@/tools/discord/join_thread'
// Member operations
import { discordKickMemberTool } from '@/tools/discord/kick_member'
import { discordLeaveThreadTool } from '@/tools/discord/leave_thread'
import { discordPinMessageTool } from '@/tools/discord/pin_message'
@@ -44,45 +35,37 @@ import { discordUpdateMemberTool } from '@/tools/discord/update_member'
import { discordUpdateRoleTool } from '@/tools/discord/update_role'
export {
// Existing tools
discordSendMessageTool,
discordGetMessagesTool,
discordGetServerTool,
discordGetUserTool,
// Message operations
discordEditMessageTool,
discordDeleteMessageTool,
discordAddReactionTool,
discordRemoveReactionTool,
discordPinMessageTool,
discordUnpinMessageTool,
// Thread operations
discordCreateThreadTool,
discordJoinThreadTool,
discordLeaveThreadTool,
discordArchiveThreadTool,
// Channel operations
discordCreateChannelTool,
discordUpdateChannelTool,
discordDeleteChannelTool,
discordGetChannelTool,
// Role operations
discordCreateRoleTool,
discordUpdateRoleTool,
discordDeleteRoleTool,
discordAssignRoleTool,
discordRemoveRoleTool,
// Member operations
discordKickMemberTool,
discordBanMemberTool,
discordUnbanMemberTool,
discordGetMemberTool,
discordUpdateMemberTool,
// Invite operations
discordCreateInviteTool,
discordGetInviteTool,
discordDeleteInviteTool,
// Webhook operations
discordCreateWebhookTool,
discordExecuteWebhookTool,
discordGetWebhookTool,

View File

@@ -57,8 +57,10 @@ export const discordUpdateChannelTool: ToolConfig<
}),
body: (params: DiscordUpdateChannelParams) => {
const body: any = {}
if (params.name) body.name = params.name
if (params.topic !== undefined) body.topic = params.topic
if (params.name !== undefined && params.name !== null && params.name !== '')
body.name = params.name
if (params.topic !== undefined && params.topic !== null && params.topic !== '')
body.topic = params.topic
return body
},
},

View File

@@ -60,9 +60,10 @@ export const discordUpdateMemberTool: ToolConfig<
}),
body: (params: DiscordUpdateMemberParams) => {
const body: any = {}
if (params.nick !== undefined) body.nick = params.nick
if (params.mute !== undefined) body.mute = params.mute
if (params.deaf !== undefined) body.deaf = params.deaf
// Note: nick can be null to remove nickname, so we allow null but not empty string
if (params.nick !== undefined && params.nick !== '') body.nick = params.nick
if (params.mute !== undefined && params.mute !== null) body.mute = params.mute
if (params.deaf !== undefined && params.deaf !== null) body.deaf = params.deaf
return body
},
},

View File

@@ -48,30 +48,6 @@ export const findSimilarLinksTool: ToolConfig<
visibility: 'user-only',
description: 'Exclude the source domain from results (default: false)',
},
startPublishedDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results published after this date (ISO 8601 format, e.g., 2024-01-01)',
},
endPublishedDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results published before this date (ISO 8601 format)',
},
startCrawlDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results crawled after this date (ISO 8601 format)',
},
endCrawlDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results crawled before this date (ISO 8601 format)',
},
category: {
type: 'string',
required: false,
@@ -137,12 +113,6 @@ export const findSimilarLinksTool: ToolConfig<
body.excludeSourceDomain = params.excludeSourceDomain
}
// Date filtering
if (params.startPublishedDate) body.startPublishedDate = params.startPublishedDate
if (params.endPublishedDate) body.endPublishedDate = params.endPublishedDate
if (params.startCrawlDate) body.startCrawlDate = params.startCrawlDate
if (params.endCrawlDate) body.endCrawlDate = params.endCrawlDate
// Category filtering
if (params.category) body.category = params.category

View File

@@ -35,7 +35,7 @@ export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> =
},
request: {
url: 'https://api.exa.ai/research/v1/',
url: 'https://api.exa.ai/research/v1',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
@@ -61,7 +61,7 @@ export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> =
return {
success: true,
output: {
taskId: data.id,
taskId: data.researchId,
research: [],
},
}
@@ -78,10 +78,11 @@ export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> =
while (elapsedTime < MAX_POLL_TIME_MS) {
try {
const statusResponse = await fetch(`https://api.exa.ai/research/v0/tasks/${taskId}`, {
const statusResponse = await fetch(`https://api.exa.ai/research/v1/${taskId}`, {
method: 'GET',
headers: {
'x-api-key': params.apiKey,
'Content-Type': 'application/json',
},
})
@@ -93,13 +94,17 @@ export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> =
logger.info(`Exa research task ${taskId} status: ${taskData.status}`)
if (taskData.status === 'completed') {
// The completed response contains output.content (text) and output.parsed (structured data)
const content =
taskData.output?.content || taskData.output?.parsed || 'Research completed successfully'
result.output = {
research: taskData.data?.results || [
research: [
{
title: 'Research Complete',
url: '',
summary: taskData.data || 'Research completed successfully',
text: undefined,
summary: typeof content === 'string' ? content : JSON.stringify(content, null, 2),
text: typeof content === 'string' ? content : JSON.stringify(content, null, 2),
publishedDate: undefined,
author: undefined,
score: 1.0,
@@ -109,11 +114,11 @@ export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> =
return result
}
if (taskData.status === 'failed') {
if (taskData.status === 'failed' || taskData.status === 'canceled') {
return {
...result,
success: false,
error: `Research task failed: ${taskData.error || 'Unknown error'}`,
error: `Research task ${taskData.status}: ${taskData.error || 'Unknown error'}`,
}
}

View File

@@ -45,30 +45,6 @@ export const searchTool: ToolConfig<ExaSearchParams, ExaSearchResponse> = {
visibility: 'user-only',
description: 'Comma-separated list of domains to exclude from results',
},
startPublishedDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results published after this date (ISO 8601 format, e.g., 2024-01-01)',
},
endPublishedDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results published before this date (ISO 8601 format)',
},
startCrawlDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results crawled after this date (ISO 8601 format)',
},
endCrawlDate: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter results crawled before this date (ISO 8601 format)',
},
category: {
type: 'string',
required: false,
@@ -139,12 +115,6 @@ export const searchTool: ToolConfig<ExaSearchParams, ExaSearchResponse> = {
.filter((d: string) => d.length > 0)
}
// Date filtering
if (params.startPublishedDate) body.startPublishedDate = params.startPublishedDate
if (params.endPublishedDate) body.endPublishedDate = params.endPublishedDate
if (params.startCrawlDate) body.startCrawlDate = params.startCrawlDate
if (params.endCrawlDate) body.endCrawlDate = params.endCrawlDate
// Category filtering
if (params.category) body.category = params.category

Some files were not shown because too many files have changed in this diff Show More