From 0a51c0db101c2c335428bb0e71ce41d618c0a43b Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 2 Oct 2026 12:24:15 -0700 Subject: [PATCH 1/2] fix(integrations): list tools for integrations without an operation dropdown The catalog generator only read operations from a block's own 'operation' dropdown, so single-tool blocks and provider pickers (DuckDuckGo, SMTP, Twilio SMS, Embeddings, ...) emitted zero operations, and Notion v2 lost its dropdown because it spreads NotionBlock.subBlocks instead of the whole config. Resolve operations through same-file subBlocks spreads, and fall back to the block's tools.access ids named from the client-safe tool metadata. --- .../deployment-config/src/integrations.json | 241 +++++++++++++++--- scripts/generate-docs.ts | 39 ++- 2 files changed, 244 insertions(+), 36 deletions(-) diff --git a/packages/deployment-config/src/integrations.json b/packages/deployment-config/src/integrations.json index db6dcd88e8c..dacec9b914a 100644 --- a/packages/deployment-config/src/integrations.json +++ b/packages/deployment-config/src/integrations.json @@ -846,8 +846,13 @@ "bgColor": "#6366F1", "iconName": "AirweaveIcon", "docsUrl": "https://docs.sim.ai/integrations/airweave", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Airweave Search", + "description": "Search your synced data collections using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results." + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -3734,8 +3739,13 @@ "bgColor": "#181C1E", "iconName": "BrowserUseIcon", "docsUrl": "https://docs.sim.ai/integrations/browser_use", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Browser Use", + "description": "Runs a browser automation task using BrowserUse" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -4366,8 +4376,13 @@ "bgColor": "#FFFFFF", "iconName": "ClayIcon", "docsUrl": "https://docs.sim.ai/integrations/clay", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Clay Populate", + "description": "Populate Clay with data from a JSON file. Enables direct communication and notifications with timestamp tracking and channel confirmation." + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -7617,8 +7632,13 @@ "bgColor": "#FFFFFF", "iconName": "DuckDuckGoIcon", "docsUrl": "https://docs.sim.ai/integrations/duckduckgo", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "DuckDuckGo Search", + "description": "Search the web using DuckDuckGo Instant Answers API. Returns instant answers, abstracts, and related topics for your query. Free to use without an API key." + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "none", @@ -8129,8 +8149,33 @@ "bgColor": "#7B4DFF", "iconName": "EmbeddingsIcon", "docsUrl": "https://docs.sim.ai/integrations/embeddings", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "OpenAI Embeddings", + "description": "Generate embeddings from text using OpenAI's embedding models" + }, + { + "name": "OpenRouter Embeddings", + "description": "Generate embeddings through OpenRouter" + }, + { + "name": "Gemini Embeddings", + "description": "Generate embeddings from text using Google's Gemini embedding models" + }, + { + "name": "Cohere Embeddings", + "description": "Generate embeddings from text using Cohere's embedding models" + }, + { + "name": "Mistral Embeddings", + "description": "Generate embeddings from text using Mistral's embedding models" + }, + { + "name": "Ollama Embeddings", + "description": "Generate embeddings on a self-hosted Ollama server" + } + ], + "operationCount": 6, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -8360,8 +8405,13 @@ "bgColor": "#000000", "iconName": "ExtendIcon", "docsUrl": "https://docs.sim.ai/integrations/extend", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Extend Document Parser", + "description": "Parse and extract content from documents using Extend AI" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -10449,8 +10499,13 @@ "bgColor": "#FFFFFF", "iconName": "GooglePagespeedIcon", "docsUrl": "https://docs.sim.ai/integrations/google_pagespeed", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Google PageSpeed Analyze", + "description": "Analyze a webpage for performance, accessibility, SEO, and best practices using Google PageSpeed Insights." + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -10467,8 +10522,13 @@ "bgColor": "#FFFFFF", "iconName": "GoogleIcon", "docsUrl": "https://docs.sim.ai/integrations/google_search", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Google Search", + "description": "Search the web with the Custom Search API" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -11835,8 +11895,13 @@ "bgColor": "#0B0F19", "iconName": "HuggingFaceIcon", "docsUrl": "https://docs.sim.ai/integrations/huggingface", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Hugging Face Chat", + "description": "Generate completions using Hugging Face Inference API" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -14435,8 +14500,13 @@ "bgColor": "#D6D3C7", "iconName": "LinkupIcon", "docsUrl": "https://docs.sim.ai/integrations/linkup", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Linkup Search", + "description": "Search the web for information using Linkup" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -16037,8 +16107,13 @@ "bgColor": "#000000", "iconName": "MistralIcon", "docsUrl": "https://docs.sim.ai/integrations/mistral_parse", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Mistral PDF Parser", + "description": "Parse PDF documents using Mistral OCR API" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -16419,8 +16494,81 @@ "bgColor": "#FFFFFF", "iconName": "NotionIcon", "docsUrl": "https://docs.sim.ai/integrations/notion", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Read Page", + "description": "Read content from a Notion page" + }, + { + "name": "Read Database", + "description": "Read database information and structure from Notion" + }, + { + "name": "Create Page", + "description": "Create a new page in Notion" + }, + { + "name": "Update Page Properties", + "description": "Update properties of a Notion page" + }, + { + "name": "Create Database", + "description": "Create a new database in Notion with custom properties" + }, + { + "name": "Add Database Row", + "description": "Add a new row to a Notion database with specified properties" + }, + { + "name": "Append Content", + "description": "Append content to a Notion page" + }, + { + "name": "Append Block Children", + "description": "Append new block children (content) to a Notion page or block" + }, + { + "name": "Retrieve Block", + "description": "Retrieve a single Notion block by its UUID, including its type-specific content" + }, + { + "name": "Retrieve Block Children", + "description": "Retrieve the block children (content) of a Notion page or block" + }, + { + "name": "Update Block", + "description": "Update the content or archived state of a single Notion block" + }, + { + "name": "Delete Block", + "description": "Delete (move to trash) a single Notion block" + }, + { + "name": "Create Comment", + "description": "Create a comment on a Notion page or within an existing discussion thread" + }, + { + "name": "List Comments", + "description": "List unresolved comments on a Notion page or block" + }, + { + "name": "List Users", + "description": "List all users (members and bots) in the Notion workspace" + }, + { + "name": "Retrieve User", + "description": "Retrieve a single Notion user by their UUID" + }, + { + "name": "Query Database", + "description": "Query and filter Notion database entries with advanced filtering" + }, + { + "name": "Search Workspace", + "description": "Search across all pages and databases in Notion workspace" + } + ], + "operationCount": 18, "triggers": [ { "id": "notion_page_created", @@ -18680,8 +18828,13 @@ "bgColor": "#FFFFFF", "iconName": "PulseIcon", "docsUrl": "https://docs.sim.ai/integrations/pulse", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Pulse Document Parser", + "description": "Parse documents (PDF, images, Office docs) using Pulse OCR API" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -19781,8 +19934,13 @@ "bgColor": "#5c0c5c", "iconName": "ReductoIcon", "docsUrl": "https://docs.sim.ai/integrations/reducto", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Reducto PDF Parser", + "description": "Parse PDF documents using Reducto OCR API" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -22203,8 +22361,13 @@ "bgColor": "#2B3543", "iconName": "SerperIcon", "docsUrl": "https://docs.sim.ai/integrations/serper", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Web Search", + "description": "A powerful web search tool that provides access to Google search results through Serper.dev API. Supports different types of searches including regular web search, news, places, images, videos, and shopping. Returns comprehensive results including organic results, knowledge graph, answer box, people also ask, related searches, and top stories." + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -23357,8 +23520,13 @@ "bgColor": "#2D3748", "iconName": "SmtpIcon", "docsUrl": "https://docs.sim.ai/integrations/smtp", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "SMTP Send Mail", + "description": "Send emails via SMTP server" + } + ], + "operationCount": 1, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -26122,8 +26290,13 @@ "bgColor": "#F22F46", "iconName": "TwilioIcon", "docsUrl": "https://docs.sim.ai/integrations/twilio_sms", - "operations": [], - "operationCount": 0, + "operations": [ + { + "name": "Twilio Send SMS", + "description": "Send text messages to single or multiple recipients using the Twilio API." + } + ], + "operationCount": 1, "triggers": [ { "id": "twilio_sms_received", diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 9a38a90a40e..957a9ab0e89 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -1951,6 +1951,7 @@ async function writeIntegrationsJson(iconMapping: Record): Prom const triggerRegistry = await buildTriggerRegistry() const { desc: toolDescMap, name: toolNameMap } = await buildToolDescriptionMap() + const toolMetadataById = await loadToolMetadata() // Hand-authored, integration-specific landing content (install walkthrough, // privacy blurb), keyed by slug. Imported as pure data — its only import is @@ -2046,6 +2047,15 @@ async function writeIntegrationsJson(iconMapping: Record): Prom return { name: label, description: opDesc } }) + // A block with no operation dropdown (single-tool blocks, provider pickers) + // exposes its tools directly, so list them from the client-safe tool metadata. + if (operations.length === 0) { + for (const toolId of toolsAccess) { + const tool = toolMetadataById[toolId] + if (!tool?.name) continue + operations.push({ name: tool.name, description: tool.description ?? '' }) + } + } const triggerIds: string[] = (config as any).triggerIds || [] const triggers: TriggerInfo[] = triggerIds @@ -2225,6 +2235,24 @@ function extractSpreadBase(blockContent: string): string | null { return spreadMatch ? spreadMatch[1] : null } +/** + * Operations a block inherits by spreading a same-file block's fields array + * (`subBlocks: [...NotionBlock.subBlocks, ...]`) rather than the whole config. + */ +function extractSpreadSubBlocksOperations( + blockContent: string, + fileContent: string | undefined +): { label: string; id: string }[] { + if (!fileContent) return [] + const declarations = blockDeclarations(fileContent) + for (const match of blockContent.matchAll(/\.\.\.(\w+Block)\.subBlocks\b/g)) { + const declaration = declarations.find((candidate) => candidate.name === match[1]) + const operations = declaration ? extractOperationsFromContent(declaration.content) : [] + if (operations.length > 0) return operations + } + return [] +} + /** Resolves a block's category through local spread ancestry without loading its registry. */ export function extractInheritedBlockCategory( blockContent: string, @@ -2325,7 +2353,14 @@ function extractBlockConfigFromContent( } } - const operations = extractOperationsFromContent(blockContent) + const ownOperations = extractOperationsFromContent(blockContent) + const baseOperations: { label: string; id: string }[] = (baseConfig as any)?.operations ?? [] + const operations = + ownOperations.length > 0 + ? ownOperations + : baseOperations.length > 0 + ? baseOperations + : extractSpreadSubBlocksOperations(blockContent, fileContent) const triggerIds = extractTriggersAvailable(blockContent, fileContent) const supplied = extractBlockSuppliedParamIds(blockContent, blockName) /** @@ -2415,7 +2450,7 @@ function extractBlockConfigFromContent( tools: { access: finalToolsAccess.length > 0 ? finalToolsAccess : baseConfig?.tools?.access || [], }, - operations: operations.length > 0 ? operations : (baseConfig as any)?.operations || [], + operations, userSettableParamIds, triggerIds: triggerIds.length > 0 ? triggerIds : (baseConfig as any)?.triggerIds || [], docsLink, From 01e8df2255ae915dbcb7b40f7c91bbe014fdf4b8 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 2 Oct 2026 12:31:16 -0700 Subject: [PATCH 2/2] fix(docs): drop the any cast and only follow plain subBlocks spreads in the catalog generator --- scripts/check-explicit-any.baseline.json | 2 +- scripts/generate-docs.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/check-explicit-any.baseline.json b/scripts/check-explicit-any.baseline.json index b8b126d33b9..a0b435321dd 100644 --- a/scripts/check-explicit-any.baseline.json +++ b/scripts/check-explicit-any.baseline.json @@ -1099,7 +1099,7 @@ "scripts/check-fork-dependent-coverage.ts": 2, "scripts/check-import-specifiers.ts": 1, "scripts/create-single-release.ts": 2, - "scripts/generate-docs.ts": 41, + "scripts/generate-docs.ts": 40, "scripts/generate-v2-cli-api.ts": 3 }, "nonNullAssertion": { diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 957a9ab0e89..77e0c0b6d71 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -2238,6 +2238,8 @@ function extractSpreadBase(blockContent: string): string | null { /** * Operations a block inherits by spreading a same-file block's fields array * (`subBlocks: [...NotionBlock.subBlocks, ...]`) rather than the whole config. + * Only a plain spread counts: a transformed one (`...XBlock.subBlocks.map(...)`) may rewrite the + * operations, so it falls through to the block's own `tools.access`. */ function extractSpreadSubBlocksOperations( blockContent: string, @@ -2245,7 +2247,7 @@ function extractSpreadSubBlocksOperations( ): { label: string; id: string }[] { if (!fileContent) return [] const declarations = blockDeclarations(fileContent) - for (const match of blockContent.matchAll(/\.\.\.(\w+Block)\.subBlocks\b/g)) { + for (const match of blockContent.matchAll(/\.\.\.(\w+Block)\.subBlocks\s*[,\]]/g)) { const declaration = declarations.find((candidate) => candidate.name === match[1]) const operations = declaration ? extractOperationsFromContent(declaration.content) : [] if (operations.length > 0) return operations @@ -2354,7 +2356,7 @@ function extractBlockConfigFromContent( } const ownOperations = extractOperationsFromContent(blockContent) - const baseOperations: { label: string; id: string }[] = (baseConfig as any)?.operations ?? [] + const baseOperations = baseConfig?.operations ?? [] const operations = ownOperations.length > 0 ? ownOperations