diff --git a/apps/docs/content/docs/en/integrations/google_groups.mdx b/apps/docs/content/docs/en/integrations/google_groups.mdx index 1e49d5fc2ab..799be5a29e1 100644 --- a/apps/docs/content/docs/en/integrations/google_groups.mdx +++ b/apps/docs/content/docs/en/integrations/google_groups.mdx @@ -352,7 +352,7 @@ Update the settings for a Google Group including access permissions, moderation, | `description` | string | No | The group description \(max 4096 characters\) | | `whoCanJoin` | string | No | Who can join: ANYONE_CAN_JOIN, ALL_IN_DOMAIN_CAN_JOIN, INVITED_CAN_JOIN, CAN_REQUEST_TO_JOIN | | `whoCanViewMembership` | string | No | Who can view membership: ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW | -| `whoCanViewGroup` | string | No | Who can view group messages: ANYONE_CAN_VIEW, ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW | +| `whoCanViewGroup` | string | No | Who can view group messages: ANYONE_CAN_VIEW, ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW, ALL_OWNERS_CAN_VIEW | | `whoCanPostMessage` | string | No | Who can post: NONE_CAN_POST, ALL_MANAGERS_CAN_POST, ALL_MEMBERS_CAN_POST, ALL_OWNERS_CAN_POST, ALL_IN_DOMAIN_CAN_POST, ANYONE_CAN_POST | | `allowExternalMembers` | string | No | Whether external users can be members: true or false | | `allowWebPosting` | string | No | Whether web posting is allowed: true or false | @@ -373,7 +373,7 @@ Update the settings for a Google Group including access permissions, moderation, | `whoCanContactOwner` | string | No | Who can contact owner: ALL_IN_DOMAIN_CAN_CONTACT, ALL_MANAGERS_CAN_CONTACT, ALL_MEMBERS_CAN_CONTACT, ANYONE_CAN_CONTACT | | `favoriteRepliesOnTop` | string | No | Whether favorite replies appear at top: true or false | | `whoCanApproveMembers` | string | No | Who can approve members: ALL_OWNERS_CAN_APPROVE, ALL_MANAGERS_CAN_APPROVE, ALL_MEMBERS_CAN_APPROVE, NONE_CAN_APPROVE | -| `whoCanBanUsers` | string | No | Who can ban users: OWNERS_ONLY, OWNERS_AND_MANAGERS, NONE | +| `whoCanBanUsers` | string | No | Who can ban users: ALL_MEMBERS, OWNERS_AND_MANAGERS, OWNERS_ONLY, NONE | | `whoCanModerateMembers` | string | No | Who can manage members: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE | | `whoCanModerateContent` | string | No | Who can moderate content: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE | | `whoCanAssistContent` | string | No | Who can assist with content metadata: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE | diff --git a/apps/docs/content/docs/en/integrations/google_maps.mdx b/apps/docs/content/docs/en/integrations/google_maps.mdx index 3fcc90c3d61..0531162db91 100644 --- a/apps/docs/content/docs/en/integrations/google_maps.mdx +++ b/apps/docs/content/docs/en/integrations/google_maps.mdx @@ -326,6 +326,43 @@ Search for places using a text query | ↳ `businessStatus` | string | Business status | | `nextPageToken` | string | Token for fetching the next page of results | +### `google_maps_pollen` + +Get a daily pollen forecast (grass, tree, weed) for a location + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Google Maps API key with Pollen API enabled | +| `lat` | number | Yes | Latitude coordinate | +| `lng` | number | Yes | Longitude coordinate | +| `days` | number | No | Number of forecast days to return \(1-5, defaults to 1\) | +| `languageCode` | string | No | Language code for the response \(e.g., "en", "es"\) | +| `plantsDescription` | boolean | No | Include detailed plant descriptions \(defaults to true\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `regionCode` | string | Region code \(ISO 3166-1 alpha-2\) for the location | +| `dailyInfo` | array | Daily pollen forecast entries | +| ↳ `date` | object | Calendar date of the forecast entry | +| ↳ `pollenTypeInfo` | array | Pollen type indices \(grass, tree, weed\) | +| ↳ `code` | string | Pollen type code \(GRASS, TREE, WEED\) | +| ↳ `displayName` | string | Display name | +| ↳ `inSeason` | boolean | Whether the pollen type is in season | +| ↳ `indexInfo` | object | Universal Pollen Index \(UPI\) info | +| ↳ `healthRecommendations` | array | Health recommendations | +| ↳ `plantInfo` | array | Per-plant forecast with descriptions | +| ↳ `code` | string | Plant code \(e.g., BIRCH, RAGWEED\) | +| ↳ `displayName` | string | Display name | +| ↳ `inSeason` | boolean | Whether the plant is in season | +| ↳ `indexInfo` | object | Universal Pollen Index \(UPI\) info | +| ↳ `plantDescription` | object | Plant details \(type, family, season, cross-reactions\) | + ### `google_maps_reverse_geocode` Convert geographic coordinates (latitude and longitude) into a human-readable address @@ -379,6 +416,36 @@ Snap GPS coordinates to the nearest road segment | ↳ `placeId` | string | Place ID for this road segment | | `warningMessage` | string | Warning message if any \(e.g., if points could not be snapped\) | +### `google_maps_solar` + +Get solar potential and panel insights for the building nearest a location + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Google Maps API key with Solar API enabled | +| `lat` | number | Yes | Latitude coordinate | +| `lng` | number | Yes | Longitude coordinate | +| `requiredQuality` | string | No | Minimum imagery quality to accept \(HIGH, MEDIUM, or BASE\) | +| `pricing` | per_request | No | No description | +| `rateLimit` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | Resource name of the building \(e.g., "buildings/ChIJ..."\) | +| `center` | object | Center coordinate of the building | +| ↳ `lat` | number | Latitude | +| ↳ `lng` | number | Longitude | +| `imageryDate` | object | Date the underlying imagery was captured | +| `imageryQuality` | string | Quality of the imagery used \(HIGH, MEDIUM, BASE\) | +| `regionCode` | string | Region code \(ISO 3166-1 alpha-2\) for the building | +| `postalCode` | string | Postal code of the building | +| `administrativeArea` | string | Administrative area \(e.g., state or province\) | +| `solarPotential` | object | Solar potential: max panel count/area, sunshine hours, carbon offset, panel specs, and configs | + ### `google_maps_speed_limits` Get speed limits for road segments. Requires either path coordinates or placeIds. diff --git a/apps/docs/content/docs/en/integrations/google_search.mdx b/apps/docs/content/docs/en/integrations/google_search.mdx index 94c9b599914..4bfd9acf387 100644 --- a/apps/docs/content/docs/en/integrations/google_search.mdx +++ b/apps/docs/content/docs/en/integrations/google_search.mdx @@ -39,7 +39,17 @@ Search the web with the Custom Search API | --------- | ---- | -------- | ----------- | | `query` | string | Yes | The search query to execute | | `searchEngineId` | string | Yes | Custom Search Engine ID | -| `num` | string | No | Number of results to return \(default: 10, max: 10\) | +| `num` | string | No | Number of results to return \(1-10, default 10\) | +| `start` | number | No | Index of the first result \(1-based, for pagination; start + num must be <= 100\) | +| `dateRestrict` | string | No | Restrict results by recency: d\[n\] days, w\[n\] weeks, m\[n\] months, y\[n\] years | +| `fileType` | string | No | Restrict to a file extension \(e.g., pdf, doc\) | +| `safe` | string | No | SafeSearch level: "active" or "off" \(default off\) | +| `searchType` | string | No | Set to "image" to perform an image search | +| `siteSearch` | string | No | A site to include or exclude from results | +| `siteSearchFilter` | string | No | Whether to include \("i"\) or exclude \("e"\) the siteSearch site | +| `lr` | string | No | Restrict to a language, e.g. "lang_en" | +| `gl` | string | No | Two-letter country code to boost geographically relevant results | +| `sort` | string | No | Sort expression, e.g. "date" | | `apiKey` | string | Yes | Google API key | #### Output @@ -48,14 +58,29 @@ Search the web with the Custom Search API | --------- | ---- | ----------- | | `items` | array | Array of search results from Google | | ↳ `title` | string | Title of the search result | +| ↳ `htmlTitle` | string | Title of the search result with HTML markup | | ↳ `link` | string | URL of the search result | -| ↳ `snippet` | string | Snippet or description of the search result | | ↳ `displayLink` | string | Display URL \(abbreviated form\) | +| ↳ `snippet` | string | Snippet or description of the search result | +| ↳ `htmlSnippet` | string | Snippet of the search result with HTML markup | +| ↳ `formattedUrl` | string | Display URL shown beneath the result | +| ↳ `mime` | string | MIME type of the result | +| ↳ `fileFormat` | string | File format of the result | +| ↳ `cacheId` | string | ID of Google's cached version | | ↳ `pagemap` | object | PageMap information for the result \(structured data\) | +| ↳ `image` | object | Image metadata \(present when searchType is image\) | +| ↳ `contextLink` | string | URL of the page hosting the image | +| ↳ `height` | number | Image height in pixels | +| ↳ `width` | number | Image width in pixels | +| ↳ `byteSize` | number | Image file size in bytes | +| ↳ `thumbnailLink` | string | Thumbnail image URL | +| ↳ `thumbnailHeight` | number | Thumbnail height in pixels | +| ↳ `thumbnailWidth` | number | Thumbnail width in pixels | | `searchInformation` | object | Information about the search query and results | | ↳ `totalResults` | string | Total number of search results available | | ↳ `searchTime` | number | Time taken to perform the search in seconds | | ↳ `formattedSearchTime` | string | Formatted search time for display | | ↳ `formattedTotalResults` | string | Formatted total results count for display | +| `nextPageStartIndex` | number | Start index for the next page of results \(null if no further results\) | diff --git a/apps/docs/content/docs/en/integrations/google_sheets.mdx b/apps/docs/content/docs/en/integrations/google_sheets.mdx index c658c1030ee..27ce97e09fe 100644 --- a/apps/docs/content/docs/en/integrations/google_sheets.mdx +++ b/apps/docs/content/docs/en/integrations/google_sheets.mdx @@ -320,6 +320,68 @@ Copy a sheet from one spreadsheet to another | `destinationSpreadsheetId` | string | The ID of the destination spreadsheet | | `destinationSpreadsheetUrl` | string | URL to the destination spreadsheet | +### `google_sheets_delete_rows` + +Delete rows from a sheet in a Google Sheets spreadsheet + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `spreadsheetId` | string | Yes | Google Sheets spreadsheet ID | +| `sheetId` | number | Yes | The numeric ID of the sheet/tab \(not the sheet name\). Use Get Spreadsheet to find sheet IDs. | +| `startIndex` | number | Yes | The start row index \(0-based, inclusive\) of the rows to delete | +| `endIndex` | number | Yes | The end row index \(0-based, exclusive\) of the rows to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spreadsheetId` | string | Google Sheets spreadsheet ID | +| `sheetId` | number | The numeric ID of the sheet | +| `deletedRowRange` | string | Description of the deleted row range | +| `metadata` | json | Spreadsheet metadata including ID and URL | +| ↳ `spreadsheetId` | string | Google Sheets spreadsheet ID | +| ↳ `spreadsheetUrl` | string | Spreadsheet URL | + +### `google_sheets_delete_sheet` + +Delete a sheet/tab from a Google Sheets spreadsheet + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `spreadsheetId` | string | Yes | Google Sheets spreadsheet ID | +| `sheetId` | number | Yes | The numeric ID of the sheet/tab to delete \(not the sheet name\). Use Get Spreadsheet to find sheet IDs. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spreadsheetId` | string | Google Sheets spreadsheet ID | +| `deletedSheetId` | number | The numeric ID of the deleted sheet | +| `metadata` | json | Spreadsheet metadata including ID and URL | +| ↳ `spreadsheetId` | string | Google Sheets spreadsheet ID | +| ↳ `spreadsheetUrl` | string | Spreadsheet URL | + +### `google_sheets_delete_spreadsheet` + +Permanently delete a Google Sheets spreadsheet using the Google Drive API + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `spreadsheetId` | string | Yes | The ID of the Google Sheets spreadsheet to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spreadsheetId` | string | The ID of the deleted spreadsheet | +| `deleted` | boolean | Whether the spreadsheet was successfully deleted | + ## Triggers diff --git a/apps/docs/content/docs/en/integrations/google_slides.mdx b/apps/docs/content/docs/en/integrations/google_slides.mdx index 591bd9dc975..9a30e156e32 100644 --- a/apps/docs/content/docs/en/integrations/google_slides.mdx +++ b/apps/docs/content/docs/en/integrations/google_slides.mdx @@ -193,7 +193,7 @@ Generate a thumbnail image of a specific slide in a Google Slides presentation | `presentationId` | string | Yes | Google Slides presentation ID | | `pageObjectId` | string | Yes | The object ID of the slide/page to get a thumbnail for | | `thumbnailSize` | string | No | The size of the thumbnail: SMALL \(200px\), MEDIUM \(800px\), or LARGE \(1600px\). Defaults to MEDIUM. | -| `mimeType` | string | No | The MIME type of the thumbnail image: PNG or GIF. Defaults to PNG. | +| `mimeType` | string | No | The MIME type of the thumbnail image: PNG. Defaults to PNG. | #### Output diff --git a/apps/sim/blocks/blocks/google.ts b/apps/sim/blocks/blocks/google.ts index 82214be7699..5c5a4da597d 100644 --- a/apps/sim/blocks/blocks/google.ts +++ b/apps/sim/blocks/blocks/google.ts @@ -61,8 +61,88 @@ Return ONLY the search query - no explanations, no quotes around the whole thing id: 'num', title: 'Number of Results', type: 'short-input', - placeholder: '10', - required: true, + placeholder: '10 (1-10)', + mode: 'advanced', + }, + { + id: 'start', + title: 'Start Index', + type: 'short-input', + placeholder: '1 (for pagination; start + num <= 100)', + mode: 'advanced', + }, + { + id: 'searchType', + title: 'Search Type', + type: 'dropdown', + options: [ + { label: 'Web', id: '' }, + { label: 'Image', id: 'image' }, + ], + mode: 'advanced', + }, + { + id: 'dateRestrict', + title: 'Date Restrict', + type: 'short-input', + placeholder: 'e.g., d7, w2, m1, y1', + mode: 'advanced', + }, + { + id: 'fileType', + title: 'File Type', + type: 'short-input', + placeholder: 'e.g., pdf, doc', + mode: 'advanced', + }, + { + id: 'safe', + title: 'SafeSearch', + type: 'dropdown', + options: [ + { label: 'Off', id: '' }, + { label: 'Active', id: 'active' }, + ], + mode: 'advanced', + }, + { + id: 'siteSearch', + title: 'Site Search', + type: 'short-input', + placeholder: 'Domain to include or exclude (e.g., wikipedia.org)', + mode: 'advanced', + }, + { + id: 'siteSearchFilter', + title: 'Site Search Filter', + type: 'dropdown', + options: [ + { label: 'Include', id: 'i' }, + { label: 'Exclude', id: 'e' }, + ], + condition: { field: 'siteSearch', value: '', not: true }, + mode: 'advanced', + }, + { + id: 'lr', + title: 'Language Restrict', + type: 'short-input', + placeholder: 'e.g., lang_en', + mode: 'advanced', + }, + { + id: 'gl', + title: 'Country (geolocation)', + type: 'short-input', + placeholder: 'Two-letter country code (e.g., us)', + mode: 'advanced', + }, + { + id: 'sort', + title: 'Sort', + type: 'short-input', + placeholder: 'e.g., date', + mode: 'advanced', }, ], @@ -74,7 +154,17 @@ Return ONLY the search query - no explanations, no quotes around the whole thing query: params.query, apiKey: params.apiKey, searchEngineId: params.searchEngineId, - num: params.num || undefined, + num: params.num ? Number(params.num) : undefined, + start: params.start ? Number(params.start) : undefined, + dateRestrict: params.dateRestrict || undefined, + fileType: params.fileType || undefined, + safe: params.safe || undefined, + searchType: params.searchType || undefined, + siteSearch: params.siteSearch || undefined, + siteSearchFilter: params.siteSearch ? params.siteSearchFilter || undefined : undefined, + lr: params.lr || undefined, + gl: params.gl || undefined, + sort: params.sort || undefined, }), }, }, @@ -83,12 +173,23 @@ Return ONLY the search query - no explanations, no quotes around the whole thing query: { type: 'string', description: 'Search query terms' }, apiKey: { type: 'string', description: 'Google API key' }, searchEngineId: { type: 'string', description: 'Custom search engine ID' }, - num: { type: 'string', description: 'Number of results' }, + num: { type: 'string', description: 'Number of results (1-10)' }, + start: { type: 'string', description: 'Start index for pagination (1-based)' }, + dateRestrict: { type: 'string', description: 'Restrict by recency (d/w/m/y notation)' }, + fileType: { type: 'string', description: 'Restrict to a file extension' }, + safe: { type: 'string', description: 'SafeSearch level (active/off)' }, + searchType: { type: 'string', description: 'Search type (image for image search)' }, + siteSearch: { type: 'string', description: 'Site to include or exclude' }, + siteSearchFilter: { type: 'string', description: 'Include (i) or exclude (e) the site' }, + lr: { type: 'string', description: 'Language restriction (e.g., lang_en)' }, + gl: { type: 'string', description: 'Country geolocation code' }, + sort: { type: 'string', description: 'Sort expression (e.g., date)' }, }, outputs: { items: { type: 'json', description: 'Search result items' }, searchInformation: { type: 'json', description: 'Search metadata' }, + nextPageStartIndex: { type: 'number', description: 'Start index for the next page of results' }, }, } diff --git a/apps/sim/blocks/blocks/google_groups.ts b/apps/sim/blocks/blocks/google_groups.ts index 76a29289064..537ae4a0123 100644 --- a/apps/sim/blocks/blocks/google_groups.ts +++ b/apps/sim/blocks/blocks/google_groups.ts @@ -337,19 +337,19 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, case 'get_group': case 'delete_group': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, } case 'create_group': return { - credential: oauthCredential, + oauthCredential, email: rest.email, name: rest.name, description: rest.description, } case 'update_group': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, name: rest.newName, email: rest.newEmail, @@ -357,7 +357,7 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, } case 'list_members': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, maxResults: rest.maxResults ? Number(rest.maxResults) : undefined, roles: rest.roles, @@ -365,38 +365,38 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, case 'get_member': case 'remove_member': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, memberKey: rest.memberKey, } case 'add_member': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, email: rest.memberEmail, role: rest.role, } case 'update_member': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, memberKey: rest.memberKey, role: rest.role, } case 'has_member': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, memberKey: rest.memberKey, } case 'list_aliases': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, } case 'add_alias': return { - credential: oauthCredential, + oauthCredential, groupKey: rest.groupKey, alias: rest.alias, } diff --git a/apps/sim/blocks/blocks/google_maps.ts b/apps/sim/blocks/blocks/google_maps.ts index ad5ed8c7e24..26d3259b7f6 100644 --- a/apps/sim/blocks/blocks/google_maps.ts +++ b/apps/sim/blocks/blocks/google_maps.ts @@ -34,6 +34,8 @@ export const GoogleMapsBlock: BlockConfig = { { label: 'Validate Address', id: 'validate_address' }, { label: 'Geolocate (WiFi/Cell)', id: 'geolocate' }, { label: 'Air Quality', id: 'air_quality' }, + { label: 'Pollen Forecast', id: 'pollen' }, + { label: 'Solar Potential', id: 'solar' }, ], value: () => 'geocode', }, @@ -354,23 +356,53 @@ export const GoogleMapsBlock: BlockConfig = { title: 'Latitude', type: 'short-input', placeholder: '37.4224764', - condition: { field: 'operation', value: 'air_quality' }, - required: { field: 'operation', value: 'air_quality' }, + condition: { field: 'operation', value: ['air_quality', 'pollen', 'solar'] }, + required: { field: 'operation', value: ['air_quality', 'pollen', 'solar'] }, }, { id: 'aqLongitude', title: 'Longitude', type: 'short-input', placeholder: '-122.0842499', - condition: { field: 'operation', value: 'air_quality' }, - required: { field: 'operation', value: 'air_quality' }, + condition: { field: 'operation', value: ['air_quality', 'pollen', 'solar'] }, + required: { field: 'operation', value: ['air_quality', 'pollen', 'solar'] }, }, { id: 'languageCode', title: 'Language Code', type: 'short-input', placeholder: 'Language code (e.g., en, es)', - condition: { field: 'operation', value: 'air_quality' }, + condition: { field: 'operation', value: ['air_quality', 'pollen'] }, + mode: 'advanced', + }, + + { + id: 'days', + title: 'Forecast Days', + type: 'short-input', + placeholder: 'Number of days (1-5, defaults to 1)', + condition: { field: 'operation', value: 'pollen' }, + mode: 'advanced', + }, + { + id: 'plantsDescription', + title: 'Include Plant Descriptions', + type: 'switch', + condition: { field: 'operation', value: 'pollen' }, + mode: 'advanced', + }, + + { + id: 'requiredQuality', + title: 'Minimum Imagery Quality', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'High', id: 'HIGH' }, + { label: 'Medium', id: 'MEDIUM' }, + { label: 'Base', id: 'BASE' }, + ], + condition: { field: 'operation', value: 'solar' }, mode: 'advanced', }, @@ -401,8 +433,10 @@ export const GoogleMapsBlock: BlockConfig = { 'google_maps_geolocate', 'google_maps_place_details', 'google_maps_places_search', + 'google_maps_pollen', 'google_maps_reverse_geocode', 'google_maps_snap_to_roads', + 'google_maps_solar', 'google_maps_speed_limits', 'google_maps_timezone', 'google_maps_validate_address', @@ -503,6 +537,18 @@ export const GoogleMapsBlock: BlockConfig = { considerIp = params.considerIp === 'true' || params.considerIp === true } + let days: number | undefined + if (params.days) { + const parsedDays = Number.parseInt(params.days, 10) + days = Number.isNaN(parsedDays) ? undefined : parsedDays + } + + let plantsDescription: boolean | undefined + if (params.plantsDescription !== undefined) { + plantsDescription = + params.plantsDescription === 'true' || params.plantsDescription === true + } + return { ...rest, address, @@ -519,6 +565,9 @@ export const GoogleMapsBlock: BlockConfig = { interpolate, enableUspsCass, considerIp, + days, + plantsDescription, + requiredQuality: params.requiredQuality || undefined, type: params.placeType || undefined, avoid: params.avoid || undefined, radioType: params.radioType || undefined, @@ -561,9 +610,12 @@ export const GoogleMapsBlock: BlockConfig = { carrier: { type: 'string', description: 'Carrier name' }, wifiAccessPoints: { type: 'string', description: 'WiFi access points JSON' }, cellTowers: { type: 'string', description: 'Cell towers JSON' }, - aqLatitude: { type: 'string', description: 'Latitude for air quality' }, - aqLongitude: { type: 'string', description: 'Longitude for air quality' }, - languageCode: { type: 'string', description: 'Language code for air quality' }, + aqLatitude: { type: 'string', description: 'Latitude for air quality, pollen, or solar' }, + aqLongitude: { type: 'string', description: 'Longitude for air quality, pollen, or solar' }, + languageCode: { type: 'string', description: 'Language code for air quality or pollen' }, + days: { type: 'string', description: 'Number of pollen forecast days (1-5)' }, + plantsDescription: { type: 'boolean', description: 'Include detailed plant descriptions' }, + requiredQuality: { type: 'string', description: 'Minimum solar imagery quality' }, }, outputs: { @@ -636,6 +688,21 @@ export const GoogleMapsBlock: BlockConfig = { indexes: { type: 'json', description: 'Air quality indexes' }, pollutants: { type: 'json', description: 'Pollutant concentrations' }, healthRecommendations: { type: 'json', description: 'Health recommendations' }, + + dailyInfo: { type: 'json', description: 'Daily pollen forecast (grass, tree, weed, plants)' }, + + center: { type: 'json', description: 'Center coordinate of the solar building' }, + imageryDate: { type: 'json', description: 'Date the solar imagery was captured' }, + imageryQuality: { type: 'string', description: 'Quality of the solar imagery used' }, + postalCode: { type: 'string', description: 'Postal code of the solar building' }, + administrativeArea: { + type: 'string', + description: 'Administrative area of the solar building', + }, + solarPotential: { + type: 'json', + description: 'Solar potential, panel specs, and configurations', + }, }, } diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts index 4578a92aae7..18397fbe736 100644 --- a/apps/sim/blocks/blocks/google_sheets.ts +++ b/apps/sim/blocks/blocks/google_sheets.ts @@ -326,6 +326,9 @@ export const GoogleSheetsV2Block: BlockConfig = { { label: 'Batch Update', id: 'batch_update' }, { label: 'Batch Clear', id: 'batch_clear' }, { label: 'Copy Sheet', id: 'copy_sheet' }, + { label: 'Delete Rows', id: 'delete_rows' }, + { label: 'Delete Sheet', id: 'delete_sheet' }, + { label: 'Delete Spreadsheet', id: 'delete_spreadsheet' }, ], value: () => 'read', }, @@ -720,6 +723,32 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, condition: { field: 'operation', value: 'copy_sheet' }, required: true, }, + // Delete Rows / Delete Sheet Fields + { + id: 'deleteSheetId', + title: 'Sheet ID', + type: 'short-input', + placeholder: 'Numeric ID of the sheet/tab (use Get Spreadsheet Info to find IDs)', + condition: { field: 'operation', value: ['delete_rows', 'delete_sheet'] }, + required: true, + }, + // Delete Rows Fields + { + id: 'startIndex', + title: 'Start Row Index', + type: 'short-input', + placeholder: '0-based, inclusive (e.g., 0 for the first row)', + condition: { field: 'operation', value: 'delete_rows' }, + required: true, + }, + { + id: 'endIndex', + title: 'End Row Index', + type: 'short-input', + placeholder: '0-based, exclusive (e.g., 5 to delete through the fifth row)', + condition: { field: 'operation', value: 'delete_rows' }, + required: true, + }, ...getTrigger('google_sheets_poller').subBlocks, ], tools: { @@ -735,6 +764,9 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, 'google_sheets_batch_update_v2', 'google_sheets_batch_clear_v2', 'google_sheets_copy_sheet_v2', + 'google_sheets_delete_rows_v2', + 'google_sheets_delete_sheet_v2', + 'google_sheets_delete_spreadsheet_v2', ], config: { tool: createVersionedToolSelector({ @@ -762,6 +794,12 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, return 'google_sheets_batch_clear' case 'copy_sheet': return 'google_sheets_copy_sheet' + case 'delete_rows': + return 'google_sheets_delete_rows' + case 'delete_sheet': + return 'google_sheets_delete_sheet' + case 'delete_spreadsheet': + return 'google_sheets_delete_spreadsheet' default: throw new Error(`Invalid Google Sheets operation: ${params.operation}`) } @@ -782,6 +820,9 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, batchData, sheetId, destinationSpreadsheetId, + deleteSheetId, + startIndex, + endIndex, filterColumn, filterValue, filterMatchType, @@ -857,6 +898,48 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, } } + // Handle delete_spreadsheet operation + if (operation === 'delete_spreadsheet') { + return { + spreadsheetId: effectiveSpreadsheetId, + oauthCredential, + } + } + + // Handle delete_sheet operation + if (operation === 'delete_sheet') { + const parsedSheetId = Number.parseInt(deleteSheetId as string, 10) + if (Number.isNaN(parsedSheetId)) { + throw new Error('Sheet ID must be a valid number') + } + return { + spreadsheetId: effectiveSpreadsheetId, + sheetId: parsedSheetId, + oauthCredential, + } + } + + // Handle delete_rows operation + if (operation === 'delete_rows') { + const parsedSheetId = Number.parseInt(deleteSheetId as string, 10) + const parsedStartIndex = Number.parseInt(startIndex as string, 10) + const parsedEndIndex = Number.parseInt(endIndex as string, 10) + if ( + Number.isNaN(parsedSheetId) || + Number.isNaN(parsedStartIndex) || + Number.isNaN(parsedEndIndex) + ) { + throw new Error('Sheet ID, start index, and end index must be valid numbers') + } + return { + spreadsheetId: effectiveSpreadsheetId, + sheetId: parsedSheetId, + startIndex: parsedStartIndex, + endIndex: parsedEndIndex, + oauthCredential, + } + } + // Handle read/write/update/append/clear operations (require sheet name) const effectiveSheetName = sheetName ? String(sheetName).trim() : '' @@ -900,6 +983,18 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, type: 'string', description: 'Destination spreadsheet ID for copy', }, + deleteSheetId: { + type: 'string', + description: 'Numeric sheet ID for delete rows/sheet operations', + }, + startIndex: { + type: 'string', + description: 'Start row index (0-based, inclusive) for delete rows operation', + }, + endIndex: { + type: 'string', + description: 'End row index (0-based, exclusive) for delete rows operation', + }, filterColumn: { type: 'string', description: 'Column header name to filter the read rows on (within the read range)', @@ -972,7 +1067,16 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, description: 'Spreadsheet ID', condition: { field: 'operation', - value: ['get_info', 'create', 'batch_get', 'batch_update', 'batch_clear'], + value: [ + 'get_info', + 'create', + 'batch_get', + 'batch_update', + 'batch_clear', + 'delete_rows', + 'delete_sheet', + 'delete_spreadsheet', + ], }, }, title: { @@ -1038,11 +1142,12 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, description: 'Array of ranges that were cleared', condition: { field: 'operation', value: 'batch_clear' }, }, - // Copy Sheet outputs + // Copy Sheet / Delete Rows outputs sheetId: { type: 'number', - description: 'ID of the copied sheet in the destination', - condition: { field: 'operation', value: 'copy_sheet' }, + description: + 'ID of the copied sheet in the destination, or the sheet the rows were deleted from', + condition: { field: 'operation', value: ['copy_sheet', 'delete_rows'] }, }, index: { type: 'number', @@ -1064,6 +1169,24 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, description: 'URL of the destination spreadsheet', condition: { field: 'operation', value: 'copy_sheet' }, }, + // Delete Rows outputs + deletedRowRange: { + type: 'string', + description: 'Description of the deleted row range', + condition: { field: 'operation', value: 'delete_rows' }, + }, + // Delete Sheet outputs + deletedSheetId: { + type: 'number', + description: 'The numeric ID of the deleted sheet', + condition: { field: 'operation', value: 'delete_sheet' }, + }, + // Delete Spreadsheet outputs + deleted: { + type: 'boolean', + description: 'Whether the spreadsheet was successfully deleted', + condition: { field: 'operation', value: 'delete_spreadsheet' }, + }, // Common metadata metadata: { type: 'json', @@ -1079,6 +1202,8 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, 'batch_get', 'batch_update', 'batch_clear', + 'delete_rows', + 'delete_sheet', ], }, }, diff --git a/apps/sim/blocks/blocks/google_slides.ts b/apps/sim/blocks/blocks/google_slides.ts index 3a36861650b..b05ecf8fde9 100644 --- a/apps/sim/blocks/blocks/google_slides.ts +++ b/apps/sim/blocks/blocks/google_slides.ts @@ -521,10 +521,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, id: 'mimeType', title: 'Image Format', type: 'dropdown', - options: [ - { label: 'PNG', id: 'PNG' }, - { label: 'GIF', id: 'GIF' }, - ], + options: [{ label: 'PNG', id: 'PNG' }], condition: { field: 'operation', value: 'get_thumbnail' }, value: () => 'PNG', }, @@ -3052,7 +3049,7 @@ Return ONLY the text content - no explanations, no markdown formatting markers, // Get thumbnail operation thumbnailPageId: { type: 'string', description: 'Slide object ID for thumbnail' }, thumbnailSize: { type: 'string', description: 'Thumbnail size' }, - mimeType: { type: 'string', description: 'Image format (PNG or GIF)' }, + mimeType: { type: 'string', description: 'Image format (PNG)' }, // Get page operation getPageObjectId: { type: 'string', description: 'Page/slide object ID to retrieve' }, // Delete object operation diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 2ff2b8f785e..45ecb7990fd 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -6408,9 +6408,17 @@ { "name": "Air Quality", "description": "Get current air quality data for a location" + }, + { + "name": "Pollen Forecast", + "description": "Get a daily pollen forecast (grass, tree, weed) for a location" + }, + { + "name": "Solar Potential", + "description": "Get solar potential and panel insights for the building nearest a location" } ], - "operationCount": 13, + "operationCount": 15, "triggers": [], "triggerCount": 0, "authType": "api-key", @@ -6551,9 +6559,21 @@ { "name": "Copy Sheet", "description": "Copy a sheet from one spreadsheet to another" + }, + { + "name": "Delete Rows", + "description": "Delete rows from a sheet in a Google Sheets spreadsheet" + }, + { + "name": "Delete Sheet", + "description": "Delete a sheet/tab from a Google Sheets spreadsheet" + }, + { + "name": "Delete Spreadsheet", + "description": "Permanently delete a Google Sheets spreadsheet using the Google Drive API" } ], - "operationCount": 11, + "operationCount": 14, "triggers": [ { "id": "google_sheets_poller", diff --git a/apps/sim/tools/google/search.ts b/apps/sim/tools/google/search.ts index 75fcc78fedc..e7ed8ad7697 100644 --- a/apps/sim/tools/google/search.ts +++ b/apps/sim/tools/google/search.ts @@ -28,7 +28,68 @@ export const searchTool: ToolConfig = type: 'string', // Treated as string for compatibility with tool interfaces required: false, visibility: 'user-only', - description: 'Number of results to return (default: 10, max: 10)', + description: 'Number of results to return (1-10, default 10)', + }, + start: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Index of the first result (1-based, for pagination; start + num must be <= 100)', + }, + dateRestrict: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Restrict results by recency: d[n] days, w[n] weeks, m[n] months, y[n] years', + }, + fileType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Restrict to a file extension (e.g., pdf, doc)', + }, + safe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'SafeSearch level: "active" or "off" (default off)', + }, + searchType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Set to "image" to perform an image search', + }, + siteSearch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'A site to include or exclude from results', + }, + siteSearchFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include ("i") or exclude ("e") the siteSearch site', + }, + lr: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Restrict to a language, e.g. "lang_en"', + }, + gl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Two-letter country code to boost geographically relevant results', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort expression, e.g. "date"', }, apiKey: { type: 'string', @@ -48,9 +109,41 @@ export const searchTool: ToolConfig = searchParams.append('q', params.query) searchParams.append('cx', params.searchEngineId) - // Add optional parameter - if (params.num) { - searchParams.append('num', params.num.toString()) + // Add optional parameters + const num = Math.trunc(Number(params.num)) + if (Number.isFinite(num) && num > 0) { + searchParams.append('num', Math.min(num, 10).toString()) + } + const start = Math.trunc(Number(params.start)) + if (Number.isFinite(start) && start > 0) { + searchParams.append('start', start.toString()) + } + if (params.dateRestrict) { + searchParams.append('dateRestrict', params.dateRestrict) + } + if (params.fileType) { + searchParams.append('fileType', params.fileType) + } + if (params.safe) { + searchParams.append('safe', params.safe) + } + if (params.searchType) { + searchParams.append('searchType', params.searchType) + } + if (params.siteSearch) { + searchParams.append('siteSearch', params.siteSearch) + if (params.siteSearchFilter) { + searchParams.append('siteSearchFilter', params.siteSearchFilter) + } + } + if (params.lr) { + searchParams.append('lr', params.lr) + } + if (params.gl) { + searchParams.append('gl', params.gl) + } + if (params.sort) { + searchParams.append('sort', params.sort) } return `${baseUrl}?${searchParams.toString()}` @@ -64,6 +157,10 @@ export const searchTool: ToolConfig = transformResponse: async (response: Response) => { const data = await response.json() + if (!response.ok || data.error) { + throw new Error(`Google Search failed: ${data.error?.message || response.statusText}`) + } + return { success: true, output: { @@ -74,6 +171,7 @@ export const searchTool: ToolConfig = formattedSearchTime: '0', formattedTotalResults: '0', }, + nextPageStartIndex: data.queries?.nextPage?.[0]?.startIndex ?? null, }, } }, @@ -92,5 +190,10 @@ export const searchTool: ToolConfig = description: 'Information about the search query and results', properties: GOOGLE_SEARCH_INFORMATION_OUTPUT_PROPERTIES, }, + nextPageStartIndex: { + type: 'number', + description: 'Start index for the next page of results (null if no further results)', + optional: true, + }, }, } diff --git a/apps/sim/tools/google/types.ts b/apps/sim/tools/google/types.ts index b9421706f6e..50a9ac8fbb2 100644 --- a/apps/sim/tools/google/types.ts +++ b/apps/sim/tools/google/types.ts @@ -11,14 +11,46 @@ import type { OutputProperty, ToolResponse } from '@/tools/types' */ export const GOOGLE_SEARCH_RESULT_OUTPUT_PROPERTIES = { title: { type: 'string', description: 'Title of the search result' }, + htmlTitle: { + type: 'string', + description: 'Title of the search result with HTML markup', + optional: true, + }, link: { type: 'string', description: 'URL of the search result' }, - snippet: { type: 'string', description: 'Snippet or description of the search result' }, displayLink: { type: 'string', description: 'Display URL (abbreviated form)', optional: true }, + snippet: { type: 'string', description: 'Snippet or description of the search result' }, + htmlSnippet: { + type: 'string', + description: 'Snippet of the search result with HTML markup', + optional: true, + }, + formattedUrl: { + type: 'string', + description: 'Display URL shown beneath the result', + optional: true, + }, + mime: { type: 'string', description: 'MIME type of the result', optional: true }, + fileFormat: { type: 'string', description: 'File format of the result', optional: true }, + cacheId: { type: 'string', description: "ID of Google's cached version", optional: true }, pagemap: { type: 'object', description: 'PageMap information for the result (structured data)', optional: true, }, + image: { + type: 'object', + description: 'Image metadata (present when searchType is image)', + optional: true, + properties: { + contextLink: { type: 'string', description: 'URL of the page hosting the image' }, + height: { type: 'number', description: 'Image height in pixels' }, + width: { type: 'number', description: 'Image width in pixels' }, + byteSize: { type: 'number', description: 'Image file size in bytes' }, + thumbnailLink: { type: 'string', description: 'Thumbnail image URL' }, + thumbnailHeight: { type: 'number', description: 'Thumbnail height in pixels' }, + thumbnailWidth: { type: 'number', description: 'Thumbnail width in pixels' }, + }, + }, } as const satisfies Record /** @@ -58,16 +90,41 @@ export interface GoogleSearchParams { apiKey: string searchEngineId: string num?: number | string + start?: number | string + dateRestrict?: string + fileType?: string + safe?: string + searchType?: string + siteSearch?: string + siteSearchFilter?: string + lr?: string + gl?: string + sort?: string } export interface GoogleSearchResponse extends ToolResponse { output: { items: Array<{ title: string + htmlTitle?: string link: string - snippet: string displayLink?: string - pagemap?: Record + snippet: string + htmlSnippet?: string + formattedUrl?: string + mime?: string + fileFormat?: string + cacheId?: string + pagemap?: Record + image?: { + contextLink?: string + height?: number + width?: number + byteSize?: number + thumbnailLink?: string + thumbnailHeight?: number + thumbnailWidth?: number + } }> searchInformation: { totalResults: string @@ -75,5 +132,6 @@ export interface GoogleSearchResponse extends ToolResponse { formattedSearchTime: string formattedTotalResults: string } + nextPageStartIndex: number | null } } diff --git a/apps/sim/tools/google_ads/ad_performance.ts b/apps/sim/tools/google_ads/ad_performance.ts index 337379298ae..e1323e514df 100644 --- a/apps/sim/tools/google_ads/ad_performance.ts +++ b/apps/sim/tools/google_ads/ad_performance.ts @@ -86,7 +86,7 @@ export const googleAdsAdPerformanceTool: ToolConfig< request: { url: (params) => { const customerId = validateNumericId(params.customerId, 'customerId') - return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + return `https://googleads.googleapis.com/v24/customers/${customerId}/googleAds:search` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/google_ads/campaign_performance.ts b/apps/sim/tools/google_ads/campaign_performance.ts index 3e4ec688b7d..47418a557c0 100644 --- a/apps/sim/tools/google_ads/campaign_performance.ts +++ b/apps/sim/tools/google_ads/campaign_performance.ts @@ -74,7 +74,7 @@ export const googleAdsCampaignPerformanceTool: ToolConfig< request: { url: (params) => { const customerId = validateNumericId(params.customerId, 'customerId') - return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + return `https://googleads.googleapis.com/v24/customers/${customerId}/googleAds:search` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/google_ads/list_ad_groups.ts b/apps/sim/tools/google_ads/list_ad_groups.ts index 8d8d2430315..e58c759fca6 100644 --- a/apps/sim/tools/google_ads/list_ad_groups.ts +++ b/apps/sim/tools/google_ads/list_ad_groups.ts @@ -67,7 +67,7 @@ export const googleAdsListAdGroupsTool: ToolConfig< request: { url: (params) => { const customerId = validateNumericId(params.customerId, 'customerId') - return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + return `https://googleads.googleapis.com/v24/customers/${customerId}/googleAds:search` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/google_ads/list_campaigns.ts b/apps/sim/tools/google_ads/list_campaigns.ts index ed738cc9558..43f543c0d83 100644 --- a/apps/sim/tools/google_ads/list_campaigns.ts +++ b/apps/sim/tools/google_ads/list_campaigns.ts @@ -61,7 +61,7 @@ export const googleAdsListCampaignsTool: ToolConfig< request: { url: (params) => { const customerId = validateNumericId(params.customerId, 'customerId') - return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + return `https://googleads.googleapis.com/v24/customers/${customerId}/googleAds:search` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/google_ads/list_customers.ts b/apps/sim/tools/google_ads/list_customers.ts index 9d764b8691c..38324213bdb 100644 --- a/apps/sim/tools/google_ads/list_customers.ts +++ b/apps/sim/tools/google_ads/list_customers.ts @@ -34,7 +34,7 @@ export const googleAdsListCustomersTool: ToolConfig< }, request: { - url: 'https://googleads.googleapis.com/v19/customers:listAccessibleCustomers', + url: 'https://googleads.googleapis.com/v24/customers:listAccessibleCustomers', method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, diff --git a/apps/sim/tools/google_ads/search.ts b/apps/sim/tools/google_ads/search.ts index ffd497fc390..74f3e260b0c 100644 --- a/apps/sim/tools/google_ads/search.ts +++ b/apps/sim/tools/google_ads/search.ts @@ -55,7 +55,7 @@ export const googleAdsSearchTool: ToolConfig { const customerId = validateNumericId(params.customerId, 'customerId') - return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + return `https://googleads.googleapis.com/v24/customers/${customerId}/googleAds:search` }, method: 'POST', headers: (params) => { diff --git a/apps/sim/tools/google_contacts/update.ts b/apps/sim/tools/google_contacts/update.ts index 51d50448d7d..0dfa7f1b3bc 100644 --- a/apps/sim/tools/google_contacts/update.ts +++ b/apps/sim/tools/google_contacts/update.ts @@ -121,6 +121,7 @@ export const updateTool: ToolConfig { const person: Record = { etag: params.etag, + metadata: { sources: [{ type: 'CONTACT', etag: params.etag }] }, } if (params.givenName || params.familyName) { diff --git a/apps/sim/tools/google_groups/add_alias.ts b/apps/sim/tools/google_groups/add_alias.ts index 4780e5db231..91d2998af60 100644 --- a/apps/sim/tools/google_groups/add_alias.ts +++ b/apps/sim/tools/google_groups/add_alias.ts @@ -47,9 +47,10 @@ export const addAliasTool: ToolConfig ({ - alias: params.alias.trim(), - }), + body: (params) => + JSON.stringify({ + alias: params.alias.trim(), + }), }, transformResponse: async (response) => { diff --git a/apps/sim/tools/google_groups/update_settings.ts b/apps/sim/tools/google_groups/update_settings.ts index 30dd92497b6..a76f235cb86 100644 --- a/apps/sim/tools/google_groups/update_settings.ts +++ b/apps/sim/tools/google_groups/update_settings.ts @@ -63,7 +63,7 @@ export const updateSettingsTool: ToolConfig< required: false, visibility: 'user-or-llm', description: - 'Who can view group messages: ANYONE_CAN_VIEW, ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW', + 'Who can view group messages: ANYONE_CAN_VIEW, ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW, ALL_OWNERS_CAN_VIEW', }, whoCanPostMessage: { type: 'string', @@ -194,7 +194,7 @@ export const updateSettingsTool: ToolConfig< type: 'string', required: false, visibility: 'user-or-llm', - description: 'Who can ban users: OWNERS_ONLY, OWNERS_AND_MANAGERS, NONE', + description: 'Who can ban users: ALL_MEMBERS, OWNERS_AND_MANAGERS, OWNERS_ONLY, NONE', }, whoCanModerateMembers: { type: 'string', @@ -297,7 +297,7 @@ export const updateSettingsTool: ToolConfig< if (params.whoCanDiscoverGroup !== undefined) body.whoCanDiscoverGroup = params.whoCanDiscoverGroup if (params.defaultSender !== undefined) body.defaultSender = params.defaultSender - return body + return JSON.stringify(body) }, }, diff --git a/apps/sim/tools/google_maps/index.ts b/apps/sim/tools/google_maps/index.ts index 8b47d6b91d3..5b3465cc088 100644 --- a/apps/sim/tools/google_maps/index.ts +++ b/apps/sim/tools/google_maps/index.ts @@ -6,8 +6,10 @@ import { googleMapsGeocodeTool } from '@/tools/google_maps/geocode' import { googleMapsGeolocateTool } from '@/tools/google_maps/geolocate' import { googleMapsPlaceDetailsTool } from '@/tools/google_maps/place_details' import { googleMapsPlacesSearchTool } from '@/tools/google_maps/places_search' +import { googleMapsPollenTool } from '@/tools/google_maps/pollen' import { googleMapsReverseGeocodeTool } from '@/tools/google_maps/reverse_geocode' import { googleMapsSnapToRoadsTool } from '@/tools/google_maps/snap_to_roads' +import { googleMapsSolarTool } from '@/tools/google_maps/solar' import { googleMapsSpeedLimitsTool } from '@/tools/google_maps/speed_limits' import { googleMapsTimezoneTool } from '@/tools/google_maps/timezone' import { googleMapsValidateAddressTool } from '@/tools/google_maps/validate_address' @@ -21,8 +23,10 @@ export { googleMapsGeolocateTool, googleMapsPlaceDetailsTool, googleMapsPlacesSearchTool, + googleMapsPollenTool, googleMapsReverseGeocodeTool, googleMapsSnapToRoadsTool, + googleMapsSolarTool, googleMapsSpeedLimitsTool, googleMapsTimezoneTool, googleMapsValidateAddressTool, diff --git a/apps/sim/tools/google_maps/pollen.ts b/apps/sim/tools/google_maps/pollen.ts new file mode 100644 index 00000000000..f38e78a23ac --- /dev/null +++ b/apps/sim/tools/google_maps/pollen.ts @@ -0,0 +1,249 @@ +import type { GoogleMapsPollenParams, GoogleMapsPollenResponse } from '@/tools/google_maps/types' +import type { ToolConfig } from '@/tools/types' + +interface RawIndexInfo { + code?: string + displayName?: string + value?: number + category?: string + indexDescription?: string + color?: { red?: number; green?: number; blue?: number } +} + +const mapIndexInfo = (indexInfo: RawIndexInfo | undefined) => + indexInfo + ? { + code: indexInfo.code || '', + displayName: indexInfo.displayName || '', + value: indexInfo.value ?? 0, + category: indexInfo.category || '', + indexDescription: indexInfo.indexDescription || '', + color: { + red: indexInfo.color?.red ?? 0, + green: indexInfo.color?.green ?? 0, + blue: indexInfo.color?.blue ?? 0, + }, + } + : null + +export const googleMapsPollenTool: ToolConfig = { + id: 'google_maps_pollen', + name: 'Google Maps Pollen', + description: 'Get a daily pollen forecast (grass, tree, weed) for a location', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Maps API key with Pollen API enabled', + }, + lat: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Latitude coordinate', + }, + lng: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Longitude coordinate', + }, + days: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of forecast days to return (1-5, defaults to 1)', + }, + languageCode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Language code for the response (e.g., "en", "es")', + }, + plantsDescription: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include detailed plant descriptions (defaults to true)', + }, + }, + + hosting: { + envKeyPrefix: 'GOOGLE_CLOUD_API_KEY', + apiKeyParam: 'apiKey', + byokProviderId: 'google_cloud', + pricing: { + type: 'per_request', + cost: 0.005, + }, + rateLimit: { + mode: 'per_request', + requestsPerMinute: 60, + }, + }, + + request: { + url: (params) => { + const url = new URL('https://pollen.googleapis.com/v1/forecast:lookup') + url.searchParams.set('location.latitude', params.lat.toString()) + url.searchParams.set('location.longitude', params.lng.toString()) + const rawDays = + typeof params.days === 'number' && Number.isFinite(params.days) + ? Math.trunc(params.days) + : 1 + const days = Math.min(Math.max(rawDays, 1), 5) + url.searchParams.set('days', days.toString()) + if (params.languageCode) { + url.searchParams.set('languageCode', params.languageCode.trim()) + } + if (params.plantsDescription !== undefined) { + url.searchParams.set('plantsDescription', String(params.plantsDescription)) + } + url.searchParams.set('key', params.apiKey.trim()) + return url.toString() + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok || data.error) { + throw new Error(`Pollen lookup failed: ${data.error?.message || response.statusText}`) + } + + const dailyInfo = (data.dailyInfo || []).map( + (day: { + date?: { year?: number; month?: number; day?: number } + pollenTypeInfo?: Array<{ + code?: string + displayName?: string + inSeason?: boolean + indexInfo?: RawIndexInfo + healthRecommendations?: string[] + }> + plantInfo?: Array<{ + code?: string + displayName?: string + inSeason?: boolean + indexInfo?: RawIndexInfo + plantDescription?: { + type?: string + family?: string + season?: string + specialColors?: string + specialShapes?: string + crossReaction?: string + picture?: string + pictureCloseup?: string + } + }> + }) => ({ + date: { + year: day.date?.year ?? 0, + month: day.date?.month ?? 0, + day: day.date?.day ?? 0, + }, + pollenTypeInfo: (day.pollenTypeInfo || []).map((type) => ({ + code: type.code || '', + displayName: type.displayName || '', + inSeason: type.inSeason ?? null, + indexInfo: mapIndexInfo(type.indexInfo), + healthRecommendations: type.healthRecommendations || [], + })), + plantInfo: (day.plantInfo || []).map((plant) => ({ + code: plant.code || '', + displayName: plant.displayName || '', + inSeason: plant.inSeason ?? null, + indexInfo: mapIndexInfo(plant.indexInfo), + plantDescription: plant.plantDescription + ? { + type: plant.plantDescription.type || '', + family: plant.plantDescription.family || '', + season: plant.plantDescription.season || '', + specialColors: plant.plantDescription.specialColors || '', + specialShapes: plant.plantDescription.specialShapes || '', + crossReaction: plant.plantDescription.crossReaction || '', + picture: plant.plantDescription.picture || '', + pictureCloseup: plant.plantDescription.pictureCloseup || '', + } + : null, + })), + }) + ) + + return { + success: true, + output: { + regionCode: data.regionCode || '', + dailyInfo, + }, + } + }, + + outputs: { + regionCode: { + type: 'string', + description: 'Region code (ISO 3166-1 alpha-2) for the location', + }, + dailyInfo: { + type: 'array', + description: 'Daily pollen forecast entries', + items: { + type: 'object', + properties: { + date: { + type: 'object', + description: 'Calendar date of the forecast entry', + properties: { + year: { type: 'number' }, + month: { type: 'number' }, + day: { type: 'number' }, + }, + }, + pollenTypeInfo: { + type: 'array', + description: 'Pollen type indices (grass, tree, weed)', + items: { + type: 'object', + properties: { + code: { type: 'string', description: 'Pollen type code (GRASS, TREE, WEED)' }, + displayName: { type: 'string', description: 'Display name' }, + inSeason: { type: 'boolean', description: 'Whether the pollen type is in season' }, + indexInfo: { type: 'object', description: 'Universal Pollen Index (UPI) info' }, + healthRecommendations: { + type: 'array', + description: 'Health recommendations', + items: { type: 'string' }, + }, + }, + }, + }, + plantInfo: { + type: 'array', + description: 'Per-plant forecast with descriptions', + items: { + type: 'object', + properties: { + code: { type: 'string', description: 'Plant code (e.g., BIRCH, RAGWEED)' }, + displayName: { type: 'string', description: 'Display name' }, + inSeason: { type: 'boolean', description: 'Whether the plant is in season' }, + indexInfo: { type: 'object', description: 'Universal Pollen Index (UPI) info' }, + plantDescription: { + type: 'object', + description: 'Plant details (type, family, season, cross-reactions)', + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/google_maps/solar.ts b/apps/sim/tools/google_maps/solar.ts new file mode 100644 index 00000000000..09fd117eff3 --- /dev/null +++ b/apps/sim/tools/google_maps/solar.ts @@ -0,0 +1,158 @@ +import type { GoogleMapsSolarParams, GoogleMapsSolarResponse } from '@/tools/google_maps/types' +import type { ToolConfig } from '@/tools/types' + +export const googleMapsSolarTool: ToolConfig = { + id: 'google_maps_solar', + name: 'Google Maps Solar', + description: 'Get solar potential and panel insights for the building nearest a location', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Maps API key with Solar API enabled', + }, + lat: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Latitude coordinate', + }, + lng: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Longitude coordinate', + }, + requiredQuality: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Minimum imagery quality to accept (HIGH, MEDIUM, or BASE)', + }, + }, + + hosting: { + envKeyPrefix: 'GOOGLE_CLOUD_API_KEY', + apiKeyParam: 'apiKey', + byokProviderId: 'google_cloud', + pricing: { + type: 'per_request', + cost: 0.005, + }, + rateLimit: { + mode: 'per_request', + requestsPerMinute: 60, + }, + }, + + request: { + url: (params) => { + const url = new URL('https://solar.googleapis.com/v1/buildingInsights:findClosest') + url.searchParams.set('location.latitude', params.lat.toString()) + url.searchParams.set('location.longitude', params.lng.toString()) + if (params.requiredQuality) { + url.searchParams.set('requiredQuality', params.requiredQuality) + } + url.searchParams.set('key', params.apiKey.trim()) + return url.toString() + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok || data.error) { + throw new Error(`Solar lookup failed: ${data.error?.message || response.statusText}`) + } + + const potential = data.solarPotential + const solarPotential = potential + ? { + maxArrayPanelsCount: potential.maxArrayPanelsCount ?? 0, + maxArrayAreaMeters2: potential.maxArrayAreaMeters2 ?? 0, + maxSunshineHoursPerYear: potential.maxSunshineHoursPerYear ?? 0, + carbonOffsetFactorKgPerMwh: potential.carbonOffsetFactorKgPerMwh ?? 0, + panelCapacityWatts: potential.panelCapacityWatts ?? 0, + panelHeightMeters: potential.panelHeightMeters ?? 0, + panelWidthMeters: potential.panelWidthMeters ?? 0, + panelLifetimeYears: potential.panelLifetimeYears ?? 0, + solarPanelConfigs: (potential.solarPanelConfigs || []).map( + (config: { panelsCount?: number; yearlyEnergyDcKwh?: number }) => ({ + panelsCount: config.panelsCount ?? 0, + yearlyEnergyDcKwh: config.yearlyEnergyDcKwh ?? 0, + }) + ), + } + : null + + return { + success: true, + output: { + name: data.name || '', + center: { + lat: data.center?.latitude ?? 0, + lng: data.center?.longitude ?? 0, + }, + imageryDate: data.imageryDate + ? { + year: data.imageryDate.year ?? 0, + month: data.imageryDate.month ?? 0, + day: data.imageryDate.day ?? 0, + } + : null, + imageryQuality: data.imageryQuality || '', + regionCode: data.regionCode || '', + postalCode: data.postalCode || '', + administrativeArea: data.administrativeArea || '', + solarPotential, + }, + } + }, + + outputs: { + name: { + type: 'string', + description: 'Resource name of the building (e.g., "buildings/ChIJ...")', + }, + center: { + type: 'object', + description: 'Center coordinate of the building', + properties: { + lat: { type: 'number', description: 'Latitude' }, + lng: { type: 'number', description: 'Longitude' }, + }, + }, + imageryDate: { + type: 'object', + description: 'Date the underlying imagery was captured', + }, + imageryQuality: { + type: 'string', + description: 'Quality of the imagery used (HIGH, MEDIUM, BASE)', + }, + regionCode: { + type: 'string', + description: 'Region code (ISO 3166-1 alpha-2) for the building', + }, + postalCode: { + type: 'string', + description: 'Postal code of the building', + }, + administrativeArea: { + type: 'string', + description: 'Administrative area (e.g., state or province)', + }, + solarPotential: { + type: 'object', + description: + 'Solar potential: max panel count/area, sunshine hours, carbon offset, panel specs, and configs', + }, + }, +} diff --git a/apps/sim/tools/google_maps/types.ts b/apps/sim/tools/google_maps/types.ts index 2c1170dd70a..d292d40f621 100644 --- a/apps/sim/tools/google_maps/types.ts +++ b/apps/sim/tools/google_maps/types.ts @@ -479,3 +479,126 @@ export interface GoogleMapsAirQualityResponse extends ToolResponse { } | null } } + +// ============================================================================ +// Pollen +// ============================================================================ + +/** + * Calendar date returned by the Pollen and Solar APIs + */ +interface GoogleMapsDate { + year: number + month: number + day: number +} + +/** + * Universal pollen index info shared by pollen types and plants + */ +interface PollenIndexInfo { + code: string + displayName: string + value: number + category: string + indexDescription: string + color: { + red: number + green: number + blue: number + } +} + +/** + * Pollen type forecast (grass, tree, weed) + */ +interface PollenTypeInfo { + code: string + displayName: string + inSeason: boolean | null + indexInfo: PollenIndexInfo | null + healthRecommendations: string[] +} + +/** + * Individual plant forecast with optional description + */ +interface PlantInfo { + code: string + displayName: string + inSeason: boolean | null + indexInfo: PollenIndexInfo | null + plantDescription: { + type: string + family: string + season: string + specialColors: string + specialShapes: string + crossReaction: string + picture: string + pictureCloseup: string + } | null +} + +interface PollenDailyInfo { + date: GoogleMapsDate + pollenTypeInfo: PollenTypeInfo[] + plantInfo: PlantInfo[] +} + +export interface GoogleMapsPollenParams { + apiKey: string + lat: number + lng: number + days?: number + languageCode?: string + plantsDescription?: boolean +} + +export interface GoogleMapsPollenResponse extends ToolResponse { + output: { + regionCode: string + dailyInfo: PollenDailyInfo[] + } +} + +// ============================================================================ +// Solar +// ============================================================================ + +interface SolarPanelConfig { + panelsCount: number + yearlyEnergyDcKwh: number +} + +interface SolarPotential { + maxArrayPanelsCount: number + maxArrayAreaMeters2: number + maxSunshineHoursPerYear: number + carbonOffsetFactorKgPerMwh: number + panelCapacityWatts: number + panelHeightMeters: number + panelWidthMeters: number + panelLifetimeYears: number + solarPanelConfigs: SolarPanelConfig[] +} + +export interface GoogleMapsSolarParams { + apiKey: string + lat: number + lng: number + requiredQuality?: 'HIGH' | 'MEDIUM' | 'BASE' +} + +export interface GoogleMapsSolarResponse extends ToolResponse { + output: { + name: string + center: LatLng + imageryDate: GoogleMapsDate | null + imageryQuality: string + regionCode: string + postalCode: string + administrativeArea: string + solarPotential: SolarPotential | null + } +} diff --git a/apps/sim/tools/google_slides/get_thumbnail.ts b/apps/sim/tools/google_slides/get_thumbnail.ts index 1bc496892c0..4ee47efbb2b 100644 --- a/apps/sim/tools/google_slides/get_thumbnail.ts +++ b/apps/sim/tools/google_slides/get_thumbnail.ts @@ -30,7 +30,7 @@ interface GetThumbnailResponse { const THUMBNAIL_SIZES = ['SMALL', 'MEDIUM', 'LARGE'] as const // Available MIME types for thumbnails -const MIME_TYPES = ['PNG', 'GIF'] as const +const MIME_TYPES = ['PNG'] as const export const getThumbnailTool: ToolConfig = { id: 'google_slides_get_thumbnail', @@ -73,7 +73,7 @@ export const getThumbnailTool: ToolConfig = { google_maps_geolocate: googleMapsGeolocateTool, google_maps_place_details: googleMapsPlaceDetailsTool, google_maps_places_search: googleMapsPlacesSearchTool, + google_maps_pollen: googleMapsPollenTool, google_maps_reverse_geocode: googleMapsReverseGeocodeTool, google_maps_snap_to_roads: googleMapsSnapToRoadsTool, + google_maps_solar: googleMapsSolarTool, google_maps_speed_limits: googleMapsSpeedLimitsTool, google_maps_timezone: googleMapsTimezoneTool, google_maps_validate_address: googleMapsValidateAddressTool,