mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(google): Maps Pollen/Solar, Custom Search expansion, and live-API fixes across Google integrations (#5113)
* feat(google): add Maps Pollen/Solar, expand Custom Search, fix Ads/Groups/Contacts/Slides New capability: - Google Maps: add Pollen Forecast and Solar Potential tools (API-key, google_cloud BYOK) - Google Custom Search: add start/dateRestrict/fileType/safe/searchType/siteSearch/ siteSearchFilter/lr/gl/sort params, htmlTitle/htmlSnippet/formattedUrl/mime/fileFormat/ cacheId/image result fields, and nextPageStartIndex pagination Fixes (validated against live API docs): - Google Ads: bump all tools from sunset v19 to v24 - Google Groups: forward OAuth credential under oauthCredential (was dropping token in 11 ops), forward all update_settings fields, JSON.stringify update_settings/add_alias bodies - Google Contacts: include required metadata.sources[].etag in updateContact body (fixed 400) - Google Slides: remove unsupported GIF thumbnail mimeType (API only allows PNG) - Google Sheets: wire delete_rows/delete_sheet/delete_spreadsheet into the V2 block - Google Custom Search: throw on API error responses instead of returning empty success; num optional + Number-coerced; pagemap typed unknown * docs(google): regenerate integration docs for new and updated operations * fix(google_maps): correct Solar requiredQuality enum to BASE The Solar API ImageryQuality enum is HIGH/MEDIUM/BASE (+ UNSPECIFIED) per the live docs; there is no LOW. Selecting "Low" sent requiredQuality=LOW which the API rejects as INVALID_ARGUMENT, and the valid BASE tier was unreachable. Replace LOW with BASE in the tool param/output descriptions, the type union, and the block dropdown. * fix(google_maps): guard !response.ok in Pollen/Solar; use ?? for color channels Address Greptile review: - Pollen and Solar transformResponse now check !response.ok || data.error (matches the Custom Search fix); a gateway error without an error key in the body no longer returns empty/zeroed output silently. - Pollen color channels use ?? instead of || so a legitimate 0 isn't treated as missing (consistent with the other numeric fields in the file). * fix(google_maps): guard against NaN days in Pollen forecast Address Cursor Bugbot: a non-numeric `days` input parsed to NaN and was forwarded as `days=NaN` (the tool's `?? 1` only catches undefined, not NaN), breaking the forecast call. The block now coerces invalid input to undefined, and the tool defaults to 1 unless `days` is a finite number. * fix(google): clamp Pollen days to 1-5; stop forwarding stale group settings fields Address Cursor Bugbot: - Pollen: clamp days to the documented 1-5 range (truncating fractionals) so 0, negatives, or >5 can't be sent to the API. - Google Groups update_settings: the block has no dedicated settings subblocks, so forwarding name/description from params could leak stale values from create_group/update_group and unintentionally rename the group. Forward only oauthCredential + groupEmail from the block (the tool's own param schema still exposes the settings fields for the agent path). * fix(google_sheets): fail fast on non-numeric delete indices Address Cursor Bugbot: delete_sheet/delete_rows parsed deleteSheetId/startIndex/ endIndex with Number.parseInt but didn't validate, so non-numeric UI input became NaN and was forwarded (the v2 delete tools only reject null/undefined), breaking the batchUpdate. The block now throws a clear error when any of these is not a valid number. * fix(google_search): clamp num to 1-10 and normalize start Address Cursor Bugbot: num was coerced with Number() but not bounded, so values like 11 or fractionals reached the API and failed. The tool now truncates and clamps num to the documented 1-10 range and only sends a positive integer start, ignoring non-numeric/out-of-range input.
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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\) |
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -326,6 +326,9 @@ export const GoogleSheetsV2Block: BlockConfig<GoogleSheetsV2Response> = {
|
||||
{ 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',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -28,7 +28,68 @@ export const searchTool: ToolConfig<GoogleSearchParams, GoogleSearchResponse> =
|
||||
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<GoogleSearchParams, GoogleSearchResponse> =
|
||||
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<GoogleSearchParams, GoogleSearchResponse> =
|
||||
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<GoogleSearchParams, GoogleSearchResponse> =
|
||||
formattedSearchTime: '0',
|
||||
formattedTotalResults: '0',
|
||||
},
|
||||
nextPageStartIndex: data.queries?.nextPage?.[0]?.startIndex ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -92,5 +190,10 @@ export const searchTool: ToolConfig<GoogleSearchParams, GoogleSearchResponse> =
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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<string, OutputProperty>
|
||||
|
||||
/**
|
||||
@@ -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<string, any>
|
||||
snippet: string
|
||||
htmlSnippet?: string
|
||||
formattedUrl?: string
|
||||
mime?: string
|
||||
fileFormat?: string
|
||||
cacheId?: string
|
||||
pagemap?: Record<string, unknown>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -55,7 +55,7 @@ export const googleAdsSearchTool: ToolConfig<GoogleAdsSearchParams, GoogleAdsSea
|
||||
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) => {
|
||||
|
||||
@@ -121,6 +121,7 @@ export const updateTool: ToolConfig<GoogleContactsUpdateParams, GoogleContactsUp
|
||||
body: (params: GoogleContactsUpdateParams) => {
|
||||
const person: Record<string, any> = {
|
||||
etag: params.etag,
|
||||
metadata: { sources: [{ type: 'CONTACT', etag: params.etag }] },
|
||||
}
|
||||
|
||||
if (params.givenName || params.familyName) {
|
||||
|
||||
@@ -47,9 +47,10 @@ export const addAliasTool: ToolConfig<GoogleGroupsAddAliasParams, GoogleGroupsAd
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
alias: params.alias.trim(),
|
||||
}),
|
||||
body: (params) =>
|
||||
JSON.stringify({
|
||||
alias: params.alias.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<GoogleMapsPollenParams, GoogleMapsPollenResponse> = {
|
||||
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)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import type { GoogleMapsSolarParams, GoogleMapsSolarResponse } from '@/tools/google_maps/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const googleMapsSolarTool: ToolConfig<GoogleMapsSolarParams, GoogleMapsSolarResponse> = {
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<GetThumbnailParams, GetThumbnailResponse> = {
|
||||
id: 'google_slides_get_thumbnail',
|
||||
@@ -73,7 +73,7 @@ export const getThumbnailTool: ToolConfig<GetThumbnailParams, GetThumbnailRespon
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The MIME type of the thumbnail image: PNG or GIF. Defaults to PNG.',
|
||||
description: 'The MIME type of the thumbnail image: PNG. Defaults to PNG.',
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -1277,8 +1277,10 @@ import {
|
||||
googleMapsGeolocateTool,
|
||||
googleMapsPlaceDetailsTool,
|
||||
googleMapsPlacesSearchTool,
|
||||
googleMapsPollenTool,
|
||||
googleMapsReverseGeocodeTool,
|
||||
googleMapsSnapToRoadsTool,
|
||||
googleMapsSolarTool,
|
||||
googleMapsSpeedLimitsTool,
|
||||
googleMapsTimezoneTool,
|
||||
googleMapsValidateAddressTool,
|
||||
@@ -5349,8 +5351,10 @@ export const tools: Record<string, 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,
|
||||
|
||||
Reference in New Issue
Block a user