feat(tools): added google maps and DSPy

This commit is contained in:
waleed
2026-01-31 00:51:22 -08:00
parent cf2f1abcaf
commit 0e7b8430e9
36 changed files with 4730 additions and 15 deletions

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,7 @@ import {
DiscordIcon,
DocumentIcon,
DropboxIcon,
DsPyIcon,
DuckDuckGoIcon,
DynamoDBIcon,
ElasticsearchIcon,
@@ -41,6 +42,7 @@ import {
GoogleFormsIcon,
GoogleGroupsIcon,
GoogleIcon,
GoogleMapsIcon,
GoogleSheetsIcon,
GoogleSlidesIcon,
GoogleVaultIcon,
@@ -153,6 +155,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
datadog: DatadogIcon,
discord: DiscordIcon,
dropbox: DropboxIcon,
dspy: DsPyIcon,
duckduckgo: DuckDuckGoIcon,
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
@@ -169,6 +172,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_drive: GoogleDriveIcon,
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_maps: GoogleMapsIcon,
google_search: GoogleIcon,
google_sheets_v2: GoogleSheetsIcon,
google_slides: GoogleSlidesIcon,

View File

@@ -1,5 +1,5 @@
---
title: CalCom
title: Cal Com
description: Manage Cal.com bookings, event types, schedules, and availability
---

View File

@@ -0,0 +1,106 @@
---
title: DSPy
description: Run predictions using self-hosted DSPy programs
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="dspy"
color="#1E293B"
/>
{/* MANUAL-CONTENT-START:intro */}
[DSPy](https://github.com/stanford-oval/dspy) is an open-source framework for programming—rather than prompting—language models. DSPy enables you to build interpretable and modular LLM-powered agents using Python functions, structured modules, and declarative signatures, making it easy to compose, debug, and reliably deploy language model applications.
With DSPy in Sim, you can:
- **Run custom predictions**: Connect your self-hosted DSPy server and invoke prediction endpoints for a variety of natural language tasks.
- **Chain of Thought and ReAct reasoning**: Leverage advanced DSPy modules for step-by-step reasoning, multi-turn dialogs, and action-observation loops.
- **Integrate with your workflows**: Automate LLM predictions and reasoning as part of any Sim automation or agent routine.
- **Provide custom endpoints and context**: Flexibly call your own DSPy-powered APIs with custom authentication, endpoints, input fields, and context.
These features let your Sim agents access modular, interpretable LLM-based programs for tasks like question answering, document analysis, decision support, and more—where you remain in control of the model, data, and logic.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate with your self-hosted DSPy programs for LLM-powered predictions. Supports Predict, Chain of Thought, and ReAct agents. DSPy is the framework for programming—not prompting—language models.
## Tools
### `dspy_predict`
Run a prediction using a self-hosted DSPy program endpoint
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `baseUrl` | string | Yes | Base URL of the DSPy server \(e.g., https://your-dspy-server.com\) |
| `apiKey` | string | No | API key for authentication \(if required by your server\) |
| `endpoint` | string | No | API endpoint path \(defaults to /predict\) |
| `input` | string | Yes | The input text to send to the DSPy program |
| `inputField` | string | No | Name of the input field expected by the DSPy program \(defaults to "text"\) |
| `context` | string | No | Additional context to provide to the DSPy program |
| `additionalInputs` | json | No | Additional key-value pairs to include in the request body |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `answer` | string | The main output/answer from the DSPy program |
| `reasoning` | string | The reasoning or rationale behind the answer \(if available\) |
| `status` | string | Response status from the DSPy server \(success or error\) |
| `rawOutput` | json | The complete raw output from the DSPy program \(result.toDict\(\)\) |
### `dspy_chain_of_thought`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `answer` | string | The answer/output from the DSPy program |
| `reasoning` | string | The reasoning or rationale behind the answer |
| `trajectory` | json | Step-by-step trajectory for ReAct \(thoughts, actions, observations\) |
| `status` | string | Response status from the DSPy server |
| `rawOutput` | json | Complete raw output from the DSPy program |
### `dspy_react`
Run a ReAct agent using a self-hosted DSPy ReAct program endpoint for multi-step reasoning and action
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `baseUrl` | string | Yes | Base URL of the DSPy server \(e.g., https://your-dspy-server.com\) |
| `apiKey` | string | No | API key for authentication \(if required by your server\) |
| `endpoint` | string | No | API endpoint path \(defaults to /predict\) |
| `task` | string | Yes | The task or question for the ReAct agent to work on |
| `context` | string | No | Additional context to provide for the task |
| `maxIterations` | number | No | Maximum number of reasoning iterations \(defaults to server setting\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `answer` | string | The final answer or result from the ReAct agent |
| `reasoning` | string | The overall reasoning summary from the agent |
| `trajectory` | array | The step-by-step trajectory of thoughts, actions, and observations |
| ↳ `thought` | string | The reasoning thought at this step |
| ↳ `toolName` | string | The name of the tool/action called |
| ↳ `toolArgs` | json | Arguments passed to the tool |
| ↳ `observation` | string | The observation/result from the tool execution |
| `status` | string | Response status from the DSPy server \(success or error\) |
| `rawOutput` | json | The complete raw output from the DSPy program \(result.toDict\(\)\) |

View File

@@ -10,6 +10,23 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#E8F0FE"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Groups](https://groups.google.com) is part of Google Workspace, providing email-based group communication, collaboration, and access control for teams and organizations. Google Groups lets you create mailing lists, manage membership, and control permissions for both internal and external users.
This page explains how you can use Sim to automate the management of Google Groups in your workflows. With Sim, agents can create and configure groups, add or remove members, update group settings, and keep directory lists up-to-date automatically—ideal for onboarding workflows, syncing IT systems, or dynamically managing project teams.
With Google Groups, you can:
- **Centralize communications**: Create team or project mailing lists for group conversations
- **Manage group membership**: Add, remove, or update members with granular roles (owner, manager, member)
- **Control access**: Manage who can view, post, or join; set permissions for public/private visibility
- **Collaborate across teams**: Streamline communication and document sharing via group-based access
- **Automate IT tasks**: Use Sim to keep group memberships current as teams change
In Sim, the Google Groups integration gives your agents API-driven control to automate common administrative tasks. Connect directly to your Google Workspace domain to add users to groups, manage lists, audit group settings, and ensure your organizations access controls are always up-to-date—without manual overhead.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Connect to Google Workspace to create, update, and manage groups and their members using the Admin SDK Directory API.

View File

@@ -0,0 +1,450 @@
---
title: Google Maps
description: Geocoding, directions, places, and distance calculations
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_maps"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Maps](https://maps.google.com) is a comprehensive platform offering a wide array of APIs for mapping, geocoding, routing, places, environment data, and more. Through Sim, your agents can leverage key Google Maps Platform APIs to automate a variety of location-based workflows.
**The following Google Maps APIs are included in this integration:**
- **Geocoding API:** Convert addresses into latitude/longitude coordinates and perform reverse geocoding.
- **Directions API:** Calculate driving, walking, cycling, or transit directions and routes between locations.
- **Distance Matrix API:** Compute travel distances and times for multiple origin and destination combinations.
- **Places API:** Search for places (businesses, landmarks, establishments) by name, type, or proximity.
- **Place Details API:** Retrieve detailed information for a specific place, such as address, ratings, hours, and contact info.
- **Elevation API:** Obtain elevation data (height above sea level) for any set of locations globally.
- **Time Zone API:** Look up time zone information for any geographic location.
- **Air Quality API:** Fetch real-time air quality data for specific coordinates.
With these APIs, your Sim agents can automate location lookup and enrichment, plan optimal routes and deliveries, estimate times and distances, analyze place data, enrich records with geographic context, get environmental conditions, and more—all without manual work or external tools.
If you need capabilities beyond what's listed here or want to request support for additional Google Maps APIs, let us know!
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Google Maps Platform APIs into your workflow. Supports geocoding addresses to coordinates, reverse geocoding, getting directions between locations, calculating distance matrices, searching for places, retrieving place details, elevation data, and timezone information.
## Tools
### `google_maps_air_quality`
Get current air quality data for a location
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key with Air Quality API enabled |
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `languageCode` | string | No | Language code for the response \(e.g., "en", "es"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dateTime` | string | Timestamp of the air quality data |
| `regionCode` | string | Region code for the location |
| `indexes` | array | Array of air quality indexes |
| ↳ `code` | string | Index code \(e.g., "uaqi", "usa_epa"\) |
| ↳ `displayName` | string | Display name of the index |
| ↳ `aqi` | number | Air quality index value |
| ↳ `aqiDisplay` | string | Formatted AQI display string |
| ↳ `color` | object | RGB color for the AQI level |
| ↳ `category` | string | Category description \(e.g., "Good", "Moderate"\) |
| ↳ `dominantPollutant` | string | The dominant pollutant |
| `pollutants` | array | Array of pollutant concentrations |
| ↳ `code` | string | Pollutant code \(e.g., "pm25", "o3"\) |
| ↳ `displayName` | string | Display name |
| ↳ `fullName` | string | Full pollutant name |
| ↳ `concentration` | object | Concentration info |
| ↳ `value` | number | Concentration value |
| ↳ `units` | string | Units \(e.g., "PARTS_PER_BILLION"\) |
| ↳ `additionalInfo` | object | Additional info about sources and effects |
| `healthRecommendations` | object | Health recommendations for different populations |
### `google_maps_directions`
Get directions and route information between two locations
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `origin` | string | Yes | Starting location \(address or lat,lng\) |
| `destination` | string | Yes | Destination location \(address or lat,lng\) |
| `mode` | string | No | Travel mode: driving, walking, bicycling, or transit |
| `avoid` | string | No | Features to avoid: tolls, highways, or ferries |
| `waypoints` | json | No | Array of intermediate waypoints |
| `units` | string | No | Unit system: metric or imperial |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `routes` | array | All available routes |
| ↳ `summary` | string | Route summary \(main road names\) |
| ↳ `legs` | array | Route legs \(segments between waypoints\) |
| ↳ `overviewPolyline` | string | Encoded polyline for the entire route |
| ↳ `warnings` | array | Route warnings |
| ↳ `waypointOrder` | array | Optimized waypoint order \(if requested\) |
| `distanceText` | string | Total distance as human-readable text \(e.g., "5.2 km"\) |
| `distanceMeters` | number | Total distance in meters |
| `durationText` | string | Total duration as human-readable text \(e.g., "15 mins"\) |
| `durationSeconds` | number | Total duration in seconds |
| `startAddress` | string | Resolved starting address |
| `endAddress` | string | Resolved ending address |
| `steps` | array | Turn-by-turn navigation instructions |
| ↳ `instruction` | string | Navigation instruction \(HTML stripped\) |
| ↳ `distanceText` | string | Step distance as text |
| ↳ `distanceMeters` | number | Step distance in meters |
| ↳ `durationText` | string | Step duration as text |
| ↳ `durationSeconds` | number | Step duration in seconds |
| ↳ `startLocation` | object | Step start coordinates |
| ↳ `endLocation` | object | Step end coordinates |
| ↳ `travelMode` | string | Travel mode for this step |
| ↳ `maneuver` | string | Maneuver type \(turn-left, etc.\) |
| `polyline` | string | Encoded polyline for the primary route |
### `google_maps_distance_matrix`
Calculate travel distance and time between multiple origins and destinations
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `origin` | string | Yes | Origin location \(address or lat,lng\) |
| `destinations` | json | Yes | Array of destination locations |
| `mode` | string | No | Travel mode: driving, walking, bicycling, or transit |
| `avoid` | string | No | Features to avoid: tolls, highways, or ferries |
| `units` | string | No | Unit system: metric or imperial |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `originAddresses` | array | Resolved origin addresses |
| `destinationAddresses` | array | Resolved destination addresses |
| `rows` | array | Distance matrix rows \(one per origin\) |
| ↳ `elements` | array | Elements \(one per destination\) |
| ↳ `distanceText` | string | Distance as text \(e.g., "5.2 km"\) |
| ↳ `distanceMeters` | number | Distance in meters |
| ↳ `durationText` | string | Duration as text \(e.g., "15 mins"\) |
| ↳ `durationSeconds` | number | Duration in seconds |
| ↳ `durationInTrafficText` | string | Duration in traffic as text |
| ↳ `durationInTrafficSeconds` | number | Duration in traffic in seconds |
| ↳ `status` | string | Element status \(OK, NOT_FOUND, ZERO_RESULTS\) |
### `google_maps_elevation`
Get elevation data for a location
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `elevation` | number | Elevation in meters above sea level \(negative for below\) |
| `lat` | number | Latitude of the elevation sample |
| `lng` | number | Longitude of the elevation sample |
| `resolution` | number | Maximum distance between data points \(meters\) from which elevation was interpolated |
### `google_maps_geocode`
Convert an address into geographic coordinates (latitude and longitude)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `address` | string | Yes | The address to geocode |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
| `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `formattedAddress` | string | The formatted address string |
| `lat` | number | Latitude coordinate |
| `lng` | number | Longitude coordinate |
| `location` | json | Location object with lat and lng |
| `placeId` | string | Google Place ID for this location |
| `addressComponents` | array | Detailed address components |
| ↳ `longName` | string | Full name of the component |
| ↳ `shortName` | string | Abbreviated name |
| ↳ `types` | array | Component types |
| `locationType` | string | Location accuracy type \(ROOFTOP, RANGE_INTERPOLATED, etc.\) |
### `google_maps_geolocate`
Geolocate a device using WiFi access points, cell towers, or IP address
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key with Geolocation API enabled |
| `homeMobileCountryCode` | number | No | Home mobile country code \(MCC\) |
| `homeMobileNetworkCode` | number | No | Home mobile network code \(MNC\) |
| `radioType` | string | No | Radio type: lte, gsm, cdma, wcdma, or nr |
| `carrier` | string | No | Carrier name |
| `considerIp` | boolean | No | Whether to use IP address for geolocation \(default: true\) |
| `cellTowers` | array | No | Array of cell tower objects with cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode |
| `wifiAccessPoints` | array | No | Array of WiFi access point objects with macAddress \(required\), signalStrength, etc. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `lat` | number | Latitude coordinate |
| `lng` | number | Longitude coordinate |
| `accuracy` | number | Accuracy radius in meters |
### `google_maps_place_details`
Get detailed information about a specific place
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `placeId` | string | Yes | Google Place ID |
| `fields` | string | No | Comma-separated list of fields to return |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `placeId` | string | Google Place ID |
| `name` | string | Place name |
| `formattedAddress` | string | Formatted street address |
| `lat` | number | Latitude coordinate |
| `lng` | number | Longitude coordinate |
| `types` | array | Place types \(e.g., restaurant, cafe\) |
| `rating` | number | Average rating \(1.0 to 5.0\) |
| `userRatingsTotal` | number | Total number of user ratings |
| `priceLevel` | number | Price level \(0=Free, 1=Inexpensive, 2=Moderate, 3=Expensive, 4=Very Expensive\) |
| `website` | string | Place website URL |
| `phoneNumber` | string | Local formatted phone number |
| `internationalPhoneNumber` | string | International formatted phone number |
| `openNow` | boolean | Whether the place is currently open |
| `weekdayText` | array | Opening hours formatted by day of week |
| `reviews` | array | User reviews \(up to 5 most relevant\) |
| ↳ `authorName` | string | Reviewer name |
| ↳ `authorUrl` | string | Reviewer profile URL |
| ↳ `profilePhotoUrl` | string | Reviewer photo URL |
| ↳ `rating` | number | Rating given \(1-5\) |
| ↳ `text` | string | Review text |
| ↳ `time` | number | Review timestamp \(Unix epoch\) |
| ↳ `relativeTimeDescription` | string | Relative time \(e.g., "a month ago"\) |
| `photos` | array | Place photos |
| ↳ `photoReference` | string | Photo reference for Place Photos API |
| ↳ `height` | number | Photo height in pixels |
| ↳ `width` | number | Photo width in pixels |
| ↳ `htmlAttributions` | array | Required attributions |
| `url` | string | Google Maps URL for the place |
| `utcOffset` | number | UTC offset in minutes |
| `vicinity` | string | Simplified address \(neighborhood/street\) |
| `businessStatus` | string | Business status \(OPERATIONAL, CLOSED_TEMPORARILY, CLOSED_PERMANENTLY\) |
### `google_maps_places_search`
Search for places using a text query
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `query` | string | Yes | Search query \(e.g., "restaurants in Times Square"\) |
| `location` | json | No | Location to bias results towards \(\{lat, lng\}\) |
| `radius` | number | No | Search radius in meters |
| `type` | string | No | Place type filter \(e.g., restaurant, cafe, hotel\) |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
| `region` | string | No | Region bias as a ccTLD code \(e.g., us, uk\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `places` | array | List of places found |
| ↳ `placeId` | string | Google Place ID |
| ↳ `name` | string | Place name |
| ↳ `formattedAddress` | string | Formatted address |
| ↳ `lat` | number | Latitude |
| ↳ `lng` | number | Longitude |
| ↳ `types` | array | Place types |
| ↳ `rating` | number | Average rating \(1-5\) |
| ↳ `userRatingsTotal` | number | Number of ratings |
| ↳ `priceLevel` | number | Price level \(0-4\) |
| ↳ `openNow` | boolean | Whether currently open |
| ↳ `photoReference` | string | Photo reference for Photos API |
| ↳ `businessStatus` | string | Business status |
| `nextPageToken` | string | Token for fetching the next page of results |
### `google_maps_reverse_geocode`
Convert geographic coordinates (latitude and longitude) into a human-readable address
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `formattedAddress` | string | The formatted address string |
| `placeId` | string | Google Place ID for this location |
| `addressComponents` | array | Detailed address components |
| ↳ `longName` | string | Full name of the component |
| ↳ `shortName` | string | Abbreviated name |
| ↳ `types` | array | Component types |
| `types` | array | Address types \(e.g., street_address, route\) |
### `google_maps_snap_to_roads`
Snap GPS coordinates to the nearest road segment
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key with Roads API enabled |
| `path` | string | Yes | Pipe-separated list of lat,lng coordinates \(e.g., "60.170880,24.942795\|60.170879,24.942796"\) |
| `interpolate` | boolean | No | Whether to interpolate additional points along the road |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `snappedPoints` | array | Array of snapped points on roads |
| ↳ `location` | object | Snapped location coordinates |
| ↳ `lat` | number | Latitude |
| ↳ `lng` | number | Longitude |
| ↳ `originalIndex` | number | Index in the original path \(if not interpolated\) |
| ↳ `placeId` | string | Place ID for this road segment |
| `warningMessage` | string | Warning message if any \(e.g., if points could not be snapped\) |
### `google_maps_speed_limits`
Get speed limits for road segments
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key with Roads API enabled |
| `path` | string | No | Pipe-separated list of lat,lng coordinates \(e.g., "60.170880,24.942795\|60.170879,24.942796"\) |
| `placeIds` | array | No | Array of Place IDs for road segments \(alternative to path\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `speedLimits` | array | Array of speed limits for road segments |
| ↳ `placeId` | string | Place ID for the road segment |
| ↳ `speedLimit` | number | Speed limit value |
| ↳ `units` | string | Speed limit units \(KPH or MPH\) |
| `snappedPoints` | array | Array of snapped points corresponding to the speed limits |
| ↳ `location` | object | Snapped location coordinates |
| ↳ `lat` | number | Latitude |
| ↳ `lng` | number | Longitude |
| ↳ `originalIndex` | number | Index in the original path |
| ↳ `placeId` | string | Place ID for this road segment |
### `google_maps_timezone`
Get timezone information for a location
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key |
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `timestamp` | number | No | Unix timestamp to determine DST offset \(defaults to current time\) |
| `language` | string | No | Language code for timezone name \(e.g., en, es, fr\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `timeZoneId` | string | IANA timezone ID \(e.g., "America/New_York", "Europe/London"\) |
| `timeZoneName` | string | Localized timezone name \(e.g., "Eastern Daylight Time"\) |
| `rawOffset` | number | UTC offset in seconds \(without DST\) |
| `dstOffset` | number | Daylight Saving Time offset in seconds \(0 if not in DST\) |
| `totalOffsetSeconds` | number | Total UTC offset in seconds \(rawOffset + dstOffset\) |
| `totalOffsetHours` | number | Total UTC offset in hours \(e.g., -5 for EST, -4 for EDT\) |
### `google_maps_validate_address`
Validate and standardize a postal address
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Maps API key with Address Validation API enabled |
| `address` | string | Yes | The address to validate \(as a single string\) |
| `regionCode` | string | No | ISO 3166-1 alpha-2 country code \(e.g., "US", "CA"\) |
| `locality` | string | No | City or locality name |
| `enableUspsCass` | boolean | No | Enable USPS CASS validation for US addresses |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `formattedAddress` | string | The standardized formatted address |
| `lat` | number | Latitude coordinate |
| `lng` | number | Longitude coordinate |
| `placeId` | string | Google Place ID for this address |
| `addressComplete` | boolean | Whether the address is complete and deliverable |
| `hasUnconfirmedComponents` | boolean | Whether some address components could not be confirmed |
| `hasInferredComponents` | boolean | Whether some components were inferred \(not in input\) |
| `hasReplacedComponents` | boolean | Whether some components were replaced with canonical values |
| `validationGranularity` | string | Granularity of validation \(PREMISE, SUB_PREMISE, ROUTE, etc.\) |
| `geocodeGranularity` | string | Granularity of the geocode result |
| `addressComponents` | array | Detailed address components |
| ↳ `longName` | string | Full name of the component |
| ↳ `shortName` | string | Abbreviated name |
| ↳ `types` | array | Component types |
| `missingComponentTypes` | array | Types of address components that are missing |
| `unconfirmedComponentTypes` | array | Types of components that could not be confirmed |
| `unresolvedTokens` | array | Input tokens that could not be resolved |

View File

@@ -19,6 +19,7 @@
"datadog",
"discord",
"dropbox",
"dspy",
"duckduckgo",
"dynamodb",
"elasticsearch",
@@ -35,6 +36,7 @@
"google_drive",
"google_forms",
"google_groups",
"google_maps",
"google_search",
"google_sheets",
"google_slides",

View File

@@ -61,12 +61,12 @@ Get comprehensive website analytics including traffic, rankings, engagement, and
| ↳ `country` | string | Country code |
| ↳ `share` | number | Traffic share \(0-1\) |
| `trafficSources` | json | Traffic source breakdown |
| ↳ `direct` | number | Direct traffic share |
| ↳ `referrals` | number | Referral traffic share |
| ↳ `search` | number | Search traffic share |
| ↳ `social` | number | Social traffic share |
| ↳ `mail` | number | Email traffic share |
| ↳ `paidReferrals` | number | Paid referral traffic share |
| ↳ `direct` | number | Direct traffic share |
| ↳ `referrals` | number | Referral traffic share |
| ↳ `search` | number | Search traffic share |
| ↳ `social` | number | Social traffic share |
| ↳ `mail` | number | Email traffic share |
| ↳ `paidReferrals` | number | Paid referral traffic share |
### `similarweb_traffic_visits`

View File

@@ -217,7 +217,7 @@ Search for videos on YouTube using the YouTube Data API. Supports advanced filte
| ↳ `channelId` | string | Channel ID that uploaded the video |
| ↳ `channelTitle` | string | Channel name |
| ↳ `publishedAt` | string | Video publish date |
| ↳ `liveBroadcastContent` | string | Live broadcast status: |
| ↳ `liveBroadcastContent` | string | Live broadcast status: "none", "live", or "upcoming" |
| `totalResults` | number | Total number of search results available |
| `nextPageToken` | string | Token for accessing the next page of results |
@@ -271,7 +271,7 @@ Get a list of video categories available on YouTube. Use this to discover valid
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `items` | array | Array of video categories available in the specified region |
| ↳ `categoryId` | string | Category ID to use in search/trending filters \(e.g., |
| ↳ `categoryId` | string | Category ID to use in search/trending filters \(e.g., "10" for Music\) |
| ↳ `title` | string | Human-readable category name |
| ↳ `assignable` | boolean | Whether videos can be tagged with this category |
| `totalResults` | number | Total number of categories available |
@@ -297,7 +297,7 @@ Get detailed information about a specific YouTube video including statistics, co
| `channelId` | string | Channel ID |
| `channelTitle` | string | Channel name |
| `publishedAt` | string | Published date and time |
| `duration` | string | Video duration in ISO 8601 format \(e.g., |
| `duration` | string | Video duration in ISO 8601 format \(e.g., "PT4M13S" for 4 min 13 sec\) |
| `viewCount` | number | Number of views |
| `likeCount` | number | Number of likes |
| `commentCount` | number | Number of comments |
@@ -305,11 +305,11 @@ Get detailed information about a specific YouTube video including statistics, co
| `thumbnail` | string | Video thumbnail URL |
| `tags` | array | Video tags |
| `categoryId` | string | YouTube video category ID |
| `definition` | string | Video definition: |
| `caption` | string | Whether captions are available: |
| `definition` | string | Video definition: "hd" or "sd" |
| `caption` | string | Whether captions are available: "true" or "false" |
| `licensedContent` | boolean | Whether the video is licensed content |
| `privacyStatus` | string | Video privacy status: |
| `liveBroadcastContent` | string | Live broadcast status: |
| `privacyStatus` | string | Video privacy status: "public", "private", or "unlisted" |
| `liveBroadcastContent` | string | Live broadcast status: "live", "upcoming", or "none" |
| `defaultLanguage` | string | Default language of the video metadata |
| `defaultAudioLanguage` | string | Default audio language of the video |
| `isLiveContent` | boolean | Whether this video is or was a live stream |

View File

@@ -6,7 +6,7 @@ import { getTrigger } from '@/triggers'
export const CalComBlock: BlockConfig<ToolResponse> = {
type: 'calcom',
name: 'CalCom',
name: 'Cal Com',
description: 'Manage Cal.com bookings, event types, schedules, and availability',
authMode: AuthMode.OAuth,
triggerAllowed: true,

View File

@@ -0,0 +1,175 @@
import { DsPyIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
export const DSPyBlock: BlockConfig = {
type: 'dspy',
name: 'DSPy',
description: 'Run predictions using self-hosted DSPy programs',
longDescription:
'Integrate with your self-hosted DSPy programs for LLM-powered predictions. Supports Predict, Chain of Thought, and ReAct agents. DSPy is the framework for programming—not prompting—language models.',
category: 'tools',
bgColor: '#1E293B',
icon: DsPyIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Predict', id: 'predict' },
{ label: 'Chain of Thought', id: 'chain_of_thought' },
{ label: 'ReAct Agent', id: 'react' },
],
value: () => 'predict',
},
{
id: 'baseUrl',
title: 'Base URL',
type: 'short-input',
placeholder: 'https://your-dspy-server.com',
required: true,
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
password: true,
placeholder: 'Optional API key for authentication',
},
{
id: 'endpoint',
title: 'Endpoint',
type: 'short-input',
placeholder: '/predict',
mode: 'advanced',
},
// Predict operation fields
{
id: 'input',
title: 'Input',
type: 'long-input',
placeholder: 'Enter your input text',
condition: { field: 'operation', value: 'predict' },
required: { field: 'operation', value: 'predict' },
rows: 4,
},
{
id: 'inputField',
title: 'Input Field Name',
type: 'short-input',
placeholder: 'text (defaults to "text")',
condition: { field: 'operation', value: 'predict' },
mode: 'advanced',
},
{
id: 'additionalInputs',
title: 'Additional Inputs',
type: 'long-input',
placeholder: '{"key": "value"} - JSON object with extra fields',
condition: { field: 'operation', value: 'predict' },
mode: 'advanced',
rows: 3,
},
// Chain of Thought operation fields
{
id: 'question',
title: 'Question',
type: 'long-input',
placeholder: 'Enter your question',
condition: { field: 'operation', value: 'chain_of_thought' },
required: { field: 'operation', value: 'chain_of_thought' },
rows: 4,
},
// ReAct operation fields
{
id: 'task',
title: 'Task',
type: 'long-input',
placeholder: 'Describe the task for the ReAct agent',
condition: { field: 'operation', value: 'react' },
required: { field: 'operation', value: 'react' },
rows: 4,
},
{
id: 'maxIterations',
title: 'Max Iterations',
type: 'short-input',
placeholder: 'Maximum reasoning iterations',
condition: { field: 'operation', value: 'react' },
mode: 'advanced',
},
// Common optional fields
{
id: 'context',
title: 'Context',
type: 'long-input',
placeholder: 'Additional context for the DSPy program',
mode: 'advanced',
rows: 4,
},
],
tools: {
access: ['dspy_predict', 'dspy_chain_of_thought', 'dspy_react'],
config: {
tool: (params) => `dspy_${params.operation}`,
params: (params) => {
const { operation, additionalInputs, maxIterations, ...rest } = params
let parsedAdditionalInputs: Record<string, unknown> | undefined
if (additionalInputs && typeof additionalInputs === 'string') {
try {
parsedAdditionalInputs = JSON.parse(additionalInputs)
} catch {
// Ignore parse errors
}
}
let parsedMaxIterations: number | undefined
if (maxIterations) {
const parsed = Number.parseInt(maxIterations as string, 10)
if (!Number.isNaN(parsed)) {
parsedMaxIterations = parsed
}
}
return {
...rest,
additionalInputs: parsedAdditionalInputs,
maxIterations: parsedMaxIterations,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'DSPy operation to perform' },
baseUrl: { type: 'string', description: 'Base URL of the DSPy server' },
apiKey: { type: 'string', description: 'API key for authentication' },
endpoint: { type: 'string', description: 'API endpoint path' },
input: { type: 'string', description: 'Input text for Predict operation' },
inputField: { type: 'string', description: 'Name of the input field' },
context: { type: 'string', description: 'Additional context for the program' },
additionalInputs: { type: 'string', description: 'JSON object with extra fields' },
question: { type: 'string', description: 'Question for Chain of Thought' },
task: { type: 'string', description: 'Task for ReAct agent' },
maxIterations: { type: 'string', description: 'Max iterations for ReAct' },
},
outputs: {
answer: { type: 'string', description: 'The answer/output from the DSPy program' },
reasoning: { type: 'string', description: 'The reasoning or rationale behind the answer' },
trajectory: {
type: 'json',
description: 'Step-by-step trajectory for ReAct (thoughts, actions, observations)',
},
status: { type: 'string', description: 'Response status from the DSPy server' },
rawOutput: { type: 'json', description: 'Complete raw output from the DSPy program' },
},
}

View File

@@ -0,0 +1,626 @@
import { GoogleMapsIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
export const GoogleMapsBlock: BlockConfig = {
type: 'google_maps',
name: 'Google Maps',
description: 'Geocoding, directions, places, and distance calculations',
longDescription:
'Integrate Google Maps Platform APIs into your workflow. Supports geocoding addresses to coordinates, reverse geocoding, getting directions between locations, calculating distance matrices, searching for places, retrieving place details, elevation data, and timezone information.',
docsLink: 'https://docs.sim.ai/tools/google_maps',
category: 'tools',
bgColor: '#E0E0E0',
icon: GoogleMapsIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Geocode Address', id: 'geocode' },
{ label: 'Reverse Geocode', id: 'reverse_geocode' },
{ label: 'Get Directions', id: 'directions' },
{ label: 'Distance Matrix', id: 'distance_matrix' },
{ label: 'Search Places', id: 'places_search' },
{ label: 'Place Details', id: 'place_details' },
{ label: 'Get Elevation', id: 'elevation' },
{ label: 'Get Timezone', id: 'timezone' },
{ label: 'Snap to Roads', id: 'snap_to_roads' },
{ label: 'Speed Limits', id: 'speed_limits' },
{ label: 'Validate Address', id: 'validate_address' },
{ label: 'Geolocate (WiFi/Cell)', id: 'geolocate' },
{ label: 'Air Quality', id: 'air_quality' },
],
value: () => 'geocode',
},
// API Key
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
password: true,
placeholder: 'Enter your Google Maps API key',
required: true,
},
// ========== Geocode ==========
{
id: 'address',
title: 'Address',
type: 'long-input',
placeholder: '1600 Amphitheatre Parkway, Mountain View, CA',
condition: { field: 'operation', value: 'geocode' },
required: { field: 'operation', value: 'geocode' },
rows: 2,
},
{
id: 'latitude',
title: 'Latitude',
type: 'short-input',
placeholder: '37.4224764',
condition: { field: 'operation', value: ['reverse_geocode', 'elevation', 'timezone'] },
required: { field: 'operation', value: ['reverse_geocode', 'elevation', 'timezone'] },
},
{
id: 'longitude',
title: 'Longitude',
type: 'short-input',
placeholder: '-122.0842499',
condition: { field: 'operation', value: ['reverse_geocode', 'elevation', 'timezone'] },
required: { field: 'operation', value: ['reverse_geocode', 'elevation', 'timezone'] },
},
{
id: 'timestamp',
title: 'Timestamp',
type: 'short-input',
placeholder: 'Unix timestamp (defaults to current time)',
condition: { field: 'operation', value: 'timezone' },
mode: 'advanced',
},
{
id: 'origin',
title: 'Origin',
type: 'short-input',
placeholder: 'Starting address or coordinates (lat,lng)',
condition: { field: 'operation', value: ['directions', 'distance_matrix'] },
required: { field: 'operation', value: ['directions', 'distance_matrix'] },
},
{
id: 'destination',
title: 'Destination',
type: 'short-input',
placeholder: 'Destination address or coordinates (lat,lng)',
condition: { field: 'operation', value: 'directions' },
required: { field: 'operation', value: 'directions' },
},
{
id: 'destinations',
title: 'Destinations',
type: 'long-input',
placeholder: 'Destination addresses separated by | (e.g., New York, NY|Boston, MA)',
condition: { field: 'operation', value: 'distance_matrix' },
required: { field: 'operation', value: 'distance_matrix' },
rows: 3,
},
{
id: 'mode',
title: 'Travel Mode',
type: 'dropdown',
options: [
{ label: 'Driving', id: 'driving' },
{ label: 'Walking', id: 'walking' },
{ label: 'Bicycling', id: 'bicycling' },
{ label: 'Transit', id: 'transit' },
],
value: () => 'driving',
condition: { field: 'operation', value: ['directions', 'distance_matrix'] },
},
{
id: 'avoid',
title: 'Avoid',
type: 'dropdown',
options: [
{ label: 'None', id: '' },
{ label: 'Tolls', id: 'tolls' },
{ label: 'Highways', id: 'highways' },
{ label: 'Ferries', id: 'ferries' },
],
condition: { field: 'operation', value: ['directions', 'distance_matrix'] },
mode: 'advanced',
},
{
id: 'waypoints',
title: 'Waypoints',
type: 'long-input',
placeholder: 'Optional stops separated by | (e.g., Stop 1|Stop 2)',
condition: { field: 'operation', value: 'directions' },
rows: 2,
mode: 'advanced',
},
{
id: 'units',
title: 'Units',
type: 'dropdown',
options: [
{ label: 'Metric (km)', id: 'metric' },
{ label: 'Imperial (miles)', id: 'imperial' },
],
value: () => 'metric',
condition: { field: 'operation', value: ['directions', 'distance_matrix'] },
mode: 'advanced',
},
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'restaurants near Times Square',
condition: { field: 'operation', value: 'places_search' },
required: { field: 'operation', value: 'places_search' },
},
{
id: 'locationBias',
title: 'Location Bias',
type: 'short-input',
placeholder: 'lat,lng to bias results (e.g., 40.7580,-73.9855)',
condition: { field: 'operation', value: 'places_search' },
mode: 'advanced',
},
{
id: 'radius',
title: 'Radius (meters)',
type: 'short-input',
placeholder: 'Search radius in meters (e.g., 5000)',
condition: { field: 'operation', value: 'places_search' },
mode: 'advanced',
},
{
id: 'placeType',
title: 'Place Type',
type: 'dropdown',
options: [
{ label: 'Any', id: '' },
{ label: 'Restaurant', id: 'restaurant' },
{ label: 'Cafe', id: 'cafe' },
{ label: 'Bar', id: 'bar' },
{ label: 'Hotel', id: 'lodging' },
{ label: 'Gas Station', id: 'gas_station' },
{ label: 'Hospital', id: 'hospital' },
{ label: 'Pharmacy', id: 'pharmacy' },
{ label: 'Bank', id: 'bank' },
{ label: 'ATM', id: 'atm' },
{ label: 'Grocery Store', id: 'supermarket' },
{ label: 'Shopping Mall', id: 'shopping_mall' },
{ label: 'Gym', id: 'gym' },
{ label: 'Park', id: 'park' },
{ label: 'Museum', id: 'museum' },
{ label: 'Movie Theater', id: 'movie_theater' },
{ label: 'Airport', id: 'airport' },
{ label: 'Train Station', id: 'train_station' },
{ label: 'Bus Station', id: 'bus_station' },
{ label: 'Parking', id: 'parking' },
],
condition: { field: 'operation', value: 'places_search' },
mode: 'advanced',
},
{
id: 'placeId',
title: 'Place ID',
type: 'short-input',
placeholder: 'Google Place ID (e.g., ChIJN1t_tDeuEmsRUsoyG83frY4)',
condition: { field: 'operation', value: 'place_details' },
required: { field: 'operation', value: 'place_details' },
},
{
id: 'fields',
title: 'Fields',
type: 'short-input',
placeholder: 'name,formatted_address,rating,opening_hours',
condition: { field: 'operation', value: 'place_details' },
mode: 'advanced',
},
{
id: 'path',
title: 'Path',
type: 'long-input',
placeholder: 'Pipe-separated lat,lng pairs (e.g., 60.170880,24.942795|60.170879,24.942796)',
condition: { field: 'operation', value: ['snap_to_roads', 'speed_limits'] },
required: { field: 'operation', value: 'snap_to_roads' },
rows: 3,
},
{
id: 'interpolate',
title: 'Interpolate',
type: 'switch',
condition: { field: 'operation', value: 'snap_to_roads' },
mode: 'advanced',
},
{
id: 'placeIds',
title: 'Place IDs',
type: 'long-input',
placeholder: 'Pipe-separated Place IDs (alternative to path)',
condition: { field: 'operation', value: 'speed_limits' },
rows: 2,
mode: 'advanced',
},
{
id: 'addressToValidate',
title: 'Address',
type: 'long-input',
placeholder: '1600 Amphitheatre Parkway, Mountain View, CA 94043',
condition: { field: 'operation', value: 'validate_address' },
required: { field: 'operation', value: 'validate_address' },
rows: 2,
},
{
id: 'regionCode',
title: 'Region Code',
type: 'short-input',
placeholder: 'ISO country code (e.g., US, CA, GB)',
condition: { field: 'operation', value: 'validate_address' },
mode: 'advanced',
},
{
id: 'locality',
title: 'Locality',
type: 'short-input',
placeholder: 'City name (optional hint)',
condition: { field: 'operation', value: 'validate_address' },
mode: 'advanced',
},
{
id: 'enableUspsCass',
title: 'Enable USPS CASS',
type: 'switch',
condition: { field: 'operation', value: 'validate_address' },
mode: 'advanced',
},
{
id: 'considerIp',
title: 'Use IP Address',
type: 'switch',
condition: { field: 'operation', value: 'geolocate' },
},
{
id: 'radioType',
title: 'Radio Type',
type: 'dropdown',
options: [
{ label: 'None', id: '' },
{ label: 'LTE', id: 'lte' },
{ label: 'GSM', id: 'gsm' },
{ label: 'CDMA', id: 'cdma' },
{ label: 'WCDMA', id: 'wcdma' },
{ label: '5G NR', id: 'nr' },
],
condition: { field: 'operation', value: 'geolocate' },
mode: 'advanced',
},
{
id: 'carrier',
title: 'Carrier',
type: 'short-input',
placeholder: 'Carrier name',
condition: { field: 'operation', value: 'geolocate' },
mode: 'advanced',
},
{
id: 'wifiAccessPoints',
title: 'WiFi Access Points',
type: 'long-input',
placeholder: 'JSON array of WiFi APs: [{"macAddress": "..."}]',
condition: { field: 'operation', value: 'geolocate' },
rows: 3,
mode: 'advanced',
},
{
id: 'cellTowers',
title: 'Cell Towers',
type: 'long-input',
placeholder: 'JSON array of cell towers: [{"cellId": ..., "locationAreaCode": ...}]',
condition: { field: 'operation', value: 'geolocate' },
rows: 3,
mode: 'advanced',
},
{
id: 'aqLatitude',
title: 'Latitude',
type: 'short-input',
placeholder: '37.4224764',
condition: { field: 'operation', value: 'air_quality' },
required: { field: 'operation', value: 'air_quality' },
},
{
id: 'aqLongitude',
title: 'Longitude',
type: 'short-input',
placeholder: '-122.0842499',
condition: { field: 'operation', value: 'air_quality' },
required: { field: 'operation', value: 'air_quality' },
},
{
id: 'languageCode',
title: 'Language Code',
type: 'short-input',
placeholder: 'Language code (e.g., en, es)',
condition: { field: 'operation', value: 'air_quality' },
mode: 'advanced',
},
{
id: 'language',
title: 'Language',
type: 'short-input',
placeholder: 'Language code (e.g., en, es, fr, de)',
mode: 'advanced',
},
{
id: 'region',
title: 'Region Bias',
type: 'short-input',
placeholder: 'Country code (e.g., us, uk, de)',
mode: 'advanced',
condition: { field: 'operation', value: ['geocode', 'places_search'] },
},
],
tools: {
access: [
'google_maps_air_quality',
'google_maps_directions',
'google_maps_distance_matrix',
'google_maps_elevation',
'google_maps_geocode',
'google_maps_geolocate',
'google_maps_place_details',
'google_maps_places_search',
'google_maps_reverse_geocode',
'google_maps_snap_to_roads',
'google_maps_speed_limits',
'google_maps_timezone',
'google_maps_validate_address',
],
config: {
tool: (params) => `google_maps_${params.operation}`,
params: (params) => {
const { operation, locationBias, ...rest } = params
let location: { lat: number; lng: number } | undefined
if (locationBias && typeof locationBias === 'string' && locationBias.includes(',')) {
const [lat, lng] = locationBias.split(',').map((s) => Number.parseFloat(s.trim()))
if (!Number.isNaN(lat) && !Number.isNaN(lng)) {
location = { lat, lng }
}
}
let lat: number | undefined
let lng: number | undefined
if (params.latitude) {
lat = Number.parseFloat(params.latitude)
}
if (params.longitude) {
lng = Number.parseFloat(params.longitude)
}
if (params.aqLatitude) {
lat = Number.parseFloat(params.aqLatitude)
}
if (params.aqLongitude) {
lng = Number.parseFloat(params.aqLongitude)
}
let timestamp: number | undefined
if (params.timestamp) {
timestamp = Number.parseInt(params.timestamp, 10)
}
let destinations: string[] | undefined
if (params.destinations && typeof params.destinations === 'string') {
destinations = params.destinations.split('|').map((d: string) => d.trim())
}
let waypoints: string[] | undefined
if (params.waypoints && typeof params.waypoints === 'string') {
waypoints = params.waypoints
.split('|')
.map((w: string) => w.trim())
.filter(Boolean)
}
let radius: number | undefined
if (params.radius) {
radius = Number.parseInt(params.radius, 10)
}
let placeIds: string[] | undefined
if (params.placeIds && typeof params.placeIds === 'string') {
placeIds = params.placeIds
.split('|')
.map((p: string) => p.trim())
.filter(Boolean)
}
let wifiAccessPoints: unknown[] | undefined
if (params.wifiAccessPoints && typeof params.wifiAccessPoints === 'string') {
try {
wifiAccessPoints = JSON.parse(params.wifiAccessPoints)
} catch {
// Ignore parse errors
}
}
let cellTowers: unknown[] | undefined
if (params.cellTowers && typeof params.cellTowers === 'string') {
try {
cellTowers = JSON.parse(params.cellTowers)
} catch {
// Ignore parse errors
}
}
const address = params.addressToValidate || params.address
// Parse boolean switches (can come as string or boolean from form)
let interpolate: boolean | undefined
if (params.interpolate !== undefined) {
interpolate = params.interpolate === 'true' || params.interpolate === true
}
let enableUspsCass: boolean | undefined
if (params.enableUspsCass !== undefined) {
enableUspsCass = params.enableUspsCass === 'true' || params.enableUspsCass === true
}
let considerIp: boolean | undefined
if (params.considerIp !== undefined) {
considerIp = params.considerIp === 'true' || params.considerIp === true
}
return {
...rest,
address,
location,
lat,
lng,
timestamp,
destinations,
waypoints,
radius,
placeIds,
wifiAccessPoints,
cellTowers,
interpolate,
enableUspsCass,
considerIp,
type: params.placeType || undefined,
avoid: params.avoid || undefined,
radioType: params.radioType || undefined,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Google Maps API key' },
address: { type: 'string', description: 'Address to geocode' },
latitude: { type: 'string', description: 'Latitude coordinate' },
longitude: { type: 'string', description: 'Longitude coordinate' },
timestamp: { type: 'string', description: 'Unix timestamp for timezone' },
origin: { type: 'string', description: 'Starting location' },
destination: { type: 'string', description: 'Destination location' },
destinations: { type: 'string', description: 'Multiple destinations (pipe-separated)' },
mode: { type: 'string', description: 'Travel mode' },
avoid: { type: 'string', description: 'Features to avoid' },
waypoints: { type: 'string', description: 'Waypoints (pipe-separated)' },
query: { type: 'string', description: 'Places search query' },
locationBias: { type: 'string', description: 'Location bias for search' },
radius: { type: 'string', description: 'Search radius in meters' },
placeType: { type: 'string', description: 'Place type filter' },
placeId: { type: 'string', description: 'Google Place ID' },
fields: { type: 'string', description: 'Fields to retrieve' },
units: { type: 'string', description: 'Unit system' },
language: { type: 'string', description: 'Response language' },
region: { type: 'string', description: 'Region bias' },
path: { type: 'string', description: 'Pipe-separated lat,lng coordinates' },
interpolate: { type: 'boolean', description: 'Interpolate points along road' },
placeIds: { type: 'string', description: 'Pipe-separated Place IDs for speed limits' },
addressToValidate: { type: 'string', description: 'Address to validate' },
regionCode: { type: 'string', description: 'ISO country code for address' },
locality: { type: 'string', description: 'City name hint' },
enableUspsCass: { type: 'boolean', description: 'Enable USPS CASS validation' },
considerIp: { type: 'boolean', description: 'Use IP for geolocation' },
radioType: { type: 'string', description: 'Radio type (lte, gsm, etc.)' },
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' },
},
outputs: {
formattedAddress: { type: 'string', description: 'Formatted address string' },
lat: { type: 'number', description: 'Latitude coordinate' },
lng: { type: 'number', description: 'Longitude coordinate' },
placeId: { type: 'string', description: 'Google Place ID' },
addressComponents: { type: 'json', description: 'Detailed address components' },
locationType: { type: 'string', description: 'Location accuracy type' },
types: { type: 'json', description: 'Address or place types' },
routes: { type: 'json', description: 'Available routes' },
distanceText: { type: 'string', description: 'Distance as text (e.g., "5.2 km")' },
distanceMeters: { type: 'number', description: 'Distance in meters' },
durationText: { type: 'string', description: 'Duration as text (e.g., "15 mins")' },
durationSeconds: { type: 'number', description: 'Duration in seconds' },
startAddress: { type: 'string', description: 'Starting address' },
endAddress: { type: 'string', description: 'Ending address' },
steps: { type: 'json', description: 'Turn-by-turn directions' },
polyline: { type: 'string', description: 'Encoded polyline for the route' },
rows: { type: 'json', description: 'Distance matrix rows' },
originAddresses: { type: 'json', description: 'Resolved origin addresses' },
destinationAddresses: { type: 'json', description: 'Resolved destination addresses' },
places: { type: 'json', description: 'List of places found' },
nextPageToken: { type: 'string', description: 'Token for next page of results' },
name: { type: 'string', description: 'Place name' },
rating: { type: 'number', description: 'Place rating (1-5)' },
userRatingsTotal: { type: 'number', description: 'Number of user ratings' },
priceLevel: { type: 'number', description: 'Price level (0-4)' },
website: { type: 'string', description: 'Place website' },
phoneNumber: { type: 'string', description: 'Place phone number' },
internationalPhoneNumber: { type: 'string', description: 'International phone number' },
openNow: { type: 'boolean', description: 'Whether place is currently open' },
weekdayText: { type: 'json', description: 'Opening hours by day' },
reviews: { type: 'json', description: 'Place reviews' },
photos: { type: 'json', description: 'Place photos' },
url: { type: 'string', description: 'Google Maps URL for the place' },
vicinity: { type: 'string', description: 'Simplified address' },
elevation: { type: 'number', description: 'Elevation in meters' },
resolution: { type: 'number', description: 'Data resolution in meters' },
timeZoneId: { type: 'string', description: 'Timezone ID (e.g., America/New_York)' },
timeZoneName: { type: 'string', description: 'Timezone display name' },
rawOffset: { type: 'number', description: 'UTC offset in seconds (without DST)' },
dstOffset: { type: 'number', description: 'DST offset in seconds' },
snappedPoints: { type: 'json', description: 'Snapped road coordinates' },
warningMessage: { type: 'string', description: 'Warning message if any' },
speedLimits: { type: 'json', description: 'Speed limits for road segments' },
addressComplete: { type: 'boolean', description: 'Whether address is complete' },
hasUnconfirmedComponents: { type: 'boolean', description: 'Has unconfirmed components' },
hasInferredComponents: { type: 'boolean', description: 'Has inferred components' },
hasReplacedComponents: { type: 'boolean', description: 'Has replaced components' },
validationGranularity: { type: 'string', description: 'Validation granularity level' },
geocodeGranularity: { type: 'string', description: 'Geocode granularity level' },
missingComponentTypes: { type: 'json', description: 'Missing address component types' },
unconfirmedComponentTypes: { type: 'json', description: 'Unconfirmed component types' },
unresolvedTokens: { type: 'json', description: 'Unresolved input tokens' },
accuracy: { type: 'number', description: 'Location accuracy in meters' },
dateTime: { type: 'string', description: 'Air quality data timestamp' },
regionCode: { type: 'string', description: 'Region code' },
indexes: { type: 'json', description: 'Air quality indexes' },
pollutants: { type: 'json', description: 'Pollutant concentrations' },
healthRecommendations: { type: 'json', description: 'Health recommendations' },
},
}

View File

@@ -21,6 +21,7 @@ import { CursorBlock, CursorV2Block } from '@/blocks/blocks/cursor'
import { DatadogBlock } from '@/blocks/blocks/datadog'
import { DiscordBlock } from '@/blocks/blocks/discord'
import { DropboxBlock } from '@/blocks/blocks/dropbox'
import { DSPyBlock } from '@/blocks/blocks/dspy'
import { DuckDuckGoBlock } from '@/blocks/blocks/duckduckgo'
import { DynamoDBBlock } from '@/blocks/blocks/dynamodb'
import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch'
@@ -41,6 +42,7 @@ import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
import { GoogleFormsBlock } from '@/blocks/blocks/google_forms'
import { GoogleGroupsBlock } from '@/blocks/blocks/google_groups'
import { GoogleMapsBlock } from '@/blocks/blocks/google_maps'
import { GoogleSheetsBlock, GoogleSheetsV2Block } from '@/blocks/blocks/google_sheets'
import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides'
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
@@ -181,6 +183,7 @@ export const registry: Record<string, BlockConfig> = {
datadog: DatadogBlock,
discord: DiscordBlock,
dropbox: DropboxBlock,
dspy: DSPyBlock,
duckduckgo: DuckDuckGoBlock,
dynamodb: DynamoDBBlock,
elasticsearch: ElasticsearchBlock,
@@ -204,6 +207,7 @@ export const registry: Record<string, BlockConfig> = {
google_drive: GoogleDriveBlock,
google_forms: GoogleFormsBlock,
google_groups: GoogleGroupsBlock,
google_maps: GoogleMapsBlock,
google_search: GoogleSearchBlock,
google_sheets: GoogleSheetsBlock,
google_sheets_v2: GoogleSheetsV2Block,

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
import type { DSPyChainOfThoughtParams, DSPyChainOfThoughtResponse } from '@/tools/dspy/types'
import type { ToolConfig } from '@/tools/types'
export const chainOfThoughtTool: ToolConfig<DSPyChainOfThoughtParams, DSPyChainOfThoughtResponse> =
{
id: 'dspy_chain_of_thought',
name: 'DSPy Chain of Thought',
description:
'Run a Chain of Thought prediction using a self-hosted DSPy ChainOfThought program endpoint',
version: '1.0.0',
params: {
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL of the DSPy server (e.g., https://your-dspy-server.com)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key for authentication (if required by your server)',
},
endpoint: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API endpoint path (defaults to /predict)',
},
question: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The question to answer using chain of thought reasoning',
},
context: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional context to provide for answering the question',
},
},
request: {
method: 'POST',
url: (params) => {
const baseUrl = params.baseUrl.replace(/\/$/, '')
const endpoint = params.endpoint || '/predict'
return `${baseUrl}${endpoint}`
},
headers: (params) => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.apiKey) {
headers.Authorization = `Bearer ${params.apiKey}`
}
return headers
},
body: (params) => {
const body: Record<string, unknown> = {
text: params.question,
}
if (params.context) {
body.context = params.context
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const status = data.status ?? 'success'
const outputData = data.data ?? data
return {
success: true,
output: {
answer: outputData.answer ?? outputData.output ?? outputData.response ?? '',
reasoning: outputData.reasoning ?? outputData.rationale ?? outputData.thought ?? '',
status,
rawOutput: outputData,
},
}
},
outputs: {
answer: {
type: 'string',
description: 'The answer generated through chain of thought reasoning',
},
reasoning: {
type: 'string',
description: 'The step-by-step reasoning that led to the answer',
},
status: {
type: 'string',
description: 'Response status from the DSPy server (success or error)',
},
rawOutput: {
type: 'json',
description: 'The complete raw output from the DSPy program (result.toDict())',
},
},
}

View File

@@ -0,0 +1,4 @@
export { chainOfThoughtTool } from '@/tools/dspy/chain-of-thought'
export { predictTool } from '@/tools/dspy/predict'
export { reactTool } from '@/tools/dspy/react'
export * from '@/tools/dspy/types'

View File

@@ -0,0 +1,125 @@
import type { DSPyPredictParams, DSPyPredictResponse } from '@/tools/dspy/types'
import type { ToolConfig } from '@/tools/types'
export const predictTool: ToolConfig<DSPyPredictParams, DSPyPredictResponse> = {
id: 'dspy_predict',
name: 'DSPy Predict',
description: 'Run a prediction using a self-hosted DSPy program endpoint',
version: '1.0.0',
params: {
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL of the DSPy server (e.g., https://your-dspy-server.com)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key for authentication (if required by your server)',
},
endpoint: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API endpoint path (defaults to /predict)',
},
input: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The input text to send to the DSPy program',
},
inputField: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Name of the input field expected by the DSPy program (defaults to "text")',
},
context: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional context to provide to the DSPy program',
},
additionalInputs: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Additional key-value pairs to include in the request body',
},
},
request: {
method: 'POST',
url: (params) => {
const baseUrl = params.baseUrl.replace(/\/$/, '')
const endpoint = params.endpoint || '/predict'
return `${baseUrl}${endpoint}`
},
headers: (params) => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.apiKey) {
headers.Authorization = `Bearer ${params.apiKey}`
}
return headers
},
body: (params) => {
const inputField = params.inputField || 'text'
const body: Record<string, unknown> = {
[inputField]: params.input,
}
if (params.context) {
body.context = params.context
}
if (params.additionalInputs) {
Object.assign(body, params.additionalInputs)
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const status = data.status ?? 'success'
const outputData = data.data ?? data
return {
success: true,
output: {
answer: outputData.answer ?? outputData.output ?? outputData.response ?? '',
reasoning: outputData.reasoning ?? outputData.rationale ?? null,
status,
rawOutput: outputData,
},
}
},
outputs: {
answer: {
type: 'string',
description: 'The main output/answer from the DSPy program',
},
reasoning: {
type: 'string',
description: 'The reasoning or rationale behind the answer (if available)',
optional: true,
},
status: {
type: 'string',
description: 'Response status from the DSPy server (success or error)',
},
rawOutput: {
type: 'json',
description: 'The complete raw output from the DSPy program (result.toDict())',
},
},
}

View File

@@ -0,0 +1,156 @@
import type { DSPyReActParams, DSPyReActResponse } from '@/tools/dspy/types'
import { parseTrajectory } from '@/tools/dspy/utils'
import type { ToolConfig } from '@/tools/types'
export const reactTool: ToolConfig<DSPyReActParams, DSPyReActResponse> = {
id: 'dspy_react',
name: 'DSPy ReAct',
description:
'Run a ReAct agent using a self-hosted DSPy ReAct program endpoint for multi-step reasoning and action',
version: '1.0.0',
params: {
baseUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Base URL of the DSPy server (e.g., https://your-dspy-server.com)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key for authentication (if required by your server)',
},
endpoint: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API endpoint path (defaults to /predict)',
},
task: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The task or question for the ReAct agent to work on',
},
context: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Additional context to provide for the task',
},
maxIterations: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of reasoning iterations (defaults to server setting)',
},
},
request: {
method: 'POST',
url: (params) => {
const baseUrl = params.baseUrl.replace(/\/$/, '')
const endpoint = params.endpoint || '/predict'
return `${baseUrl}${endpoint}`
},
headers: (params) => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.apiKey) {
headers.Authorization = `Bearer ${params.apiKey}`
}
return headers
},
body: (params) => {
const body: Record<string, unknown> = {
text: params.task,
}
if (params.context) {
body.context = params.context
}
if (params.maxIterations !== undefined) {
body.max_iters = params.maxIterations
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const status = data.status ?? 'success'
const outputData = data.data ?? data
const rawTrajectory = outputData.trajectory ?? {}
const trajectory = Array.isArray(rawTrajectory)
? rawTrajectory.map((step: Record<string, unknown>) => ({
thought: (step.thought as string) ?? (step.reasoning as string) ?? '',
toolName: (step.tool_name as string) ?? (step.selected_fn as string) ?? '',
toolArgs:
(step.tool_args as Record<string, unknown>) ??
(step.args as Record<string, unknown>) ??
{},
observation: step.observation !== undefined ? String(step.observation) : null,
}))
: parseTrajectory(rawTrajectory)
return {
success: true,
output: {
answer:
outputData.answer ??
outputData.process_result ??
outputData.output ??
outputData.response ??
'',
reasoning: outputData.reasoning ?? null,
trajectory,
status,
rawOutput: outputData,
},
}
},
outputs: {
answer: {
type: 'string',
description: 'The final answer or result from the ReAct agent',
},
reasoning: {
type: 'string',
description: 'The overall reasoning summary from the agent',
optional: true,
},
trajectory: {
type: 'array',
description: 'The step-by-step trajectory of thoughts, actions, and observations',
items: {
type: 'object',
properties: {
thought: { type: 'string', description: 'The reasoning thought at this step' },
toolName: { type: 'string', description: 'The name of the tool/action called' },
toolArgs: { type: 'json', description: 'Arguments passed to the tool' },
observation: {
type: 'string',
description: 'The observation/result from the tool execution',
optional: true,
},
},
},
},
status: {
type: 'string',
description: 'Response status from the DSPy server (success or error)',
},
rawOutput: {
type: 'json',
description: 'The complete raw output from the DSPy program (result.toDict())',
},
},
}

View File

@@ -0,0 +1,84 @@
import type { ToolResponse } from '@/tools/types'
/**
* Parameters for running a DSPy prediction
*/
export interface DSPyPredictParams {
baseUrl: string
apiKey?: string
endpoint?: string
input: string
inputField?: string
context?: string
additionalInputs?: Record<string, unknown>
}
/**
* Response from a DSPy prediction
*/
export interface DSPyPredictResponse extends ToolResponse {
output: {
answer: string
reasoning: string | null
status: string
rawOutput: Record<string, unknown>
}
}
/**
* Parameters for running a DSPy Chain of Thought prediction
*/
export interface DSPyChainOfThoughtParams {
baseUrl: string
apiKey?: string
endpoint?: string
question: string
context?: string
}
/**
* Response from a DSPy Chain of Thought prediction
*/
export interface DSPyChainOfThoughtResponse extends ToolResponse {
output: {
answer: string
reasoning: string
status: string
rawOutput: Record<string, unknown>
}
}
/**
* Parameters for running a DSPy ReAct agent
*/
export interface DSPyReActParams {
baseUrl: string
apiKey?: string
endpoint?: string
task: string
context?: string
maxIterations?: number
}
/**
* ReAct trajectory step structure (matches DSPy output format)
*/
export interface DSPyTrajectoryStep {
thought: string
toolName: string
toolArgs: Record<string, unknown>
observation: string | null
}
/**
* Response from a DSPy ReAct agent
*/
export interface DSPyReActResponse extends ToolResponse {
output: {
answer: string
reasoning: string | null
trajectory: DSPyTrajectoryStep[]
status: string
rawOutput: Record<string, unknown>
}
}

View File

@@ -0,0 +1,28 @@
import type { DSPyTrajectoryStep } from '@/tools/dspy/types'
/**
* Parse DSPy ReAct trajectory format into structured steps
* DSPy trajectory format: { thought_0, tool_name_0, tool_args_0, observation_0, thought_1, ... }
*/
export function parseTrajectory(trajectory: Record<string, unknown>): DSPyTrajectoryStep[] {
const steps: DSPyTrajectoryStep[] = []
let idx = 0
while (
trajectory[`thought_${idx}`] !== undefined ||
trajectory[`tool_name_${idx}`] !== undefined
) {
steps.push({
thought: (trajectory[`thought_${idx}`] as string) ?? '',
toolName: (trajectory[`tool_name_${idx}`] as string) ?? '',
toolArgs: (trajectory[`tool_args_${idx}`] as Record<string, unknown>) ?? {},
observation:
trajectory[`observation_${idx}`] !== undefined
? String(trajectory[`observation_${idx}`])
: null,
})
idx++
}
return steps
}

View File

@@ -0,0 +1,223 @@
import type {
GoogleMapsAirQualityParams,
GoogleMapsAirQualityResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsAirQualityTool: ToolConfig<
GoogleMapsAirQualityParams,
GoogleMapsAirQualityResponse
> = {
id: 'google_maps_air_quality',
name: 'Google Maps Air Quality',
description: 'Get current air quality data for a location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Air Quality 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',
},
languageCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for the response (e.g., "en", "es")',
},
},
request: {
url: (params) => {
return `https://airquality.googleapis.com/v1/currentConditions:lookup?key=${params.apiKey.trim()}`
},
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => {
const body: {
location: { latitude: number; longitude: number }
extraComputations: string[]
languageCode?: string
} = {
location: {
latitude: params.lat,
longitude: params.lng,
},
extraComputations: [
'HEALTH_RECOMMENDATIONS',
'DOMINANT_POLLUTANT_CONCENTRATION',
'POLLUTANT_CONCENTRATION',
'LOCAL_AQI',
'POLLUTANT_ADDITIONAL_INFO',
],
}
if (params.languageCode) {
body.languageCode = params.languageCode.trim()
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.error) {
throw new Error(`Air Quality failed: ${data.error.message || 'Unknown error'}`)
}
const indexes = (data.indexes || []).map(
(index: {
code: string
displayName: string
aqi: number
aqiDisplay: string
color: { red?: number; green?: number; blue?: number }
category: string
dominantPollutant: string
}) => ({
code: index.code,
displayName: index.displayName,
aqi: index.aqi,
aqiDisplay: index.aqiDisplay,
color: {
red: index.color?.red || 0,
green: index.color?.green || 0,
blue: index.color?.blue || 0,
},
category: index.category,
dominantPollutant: index.dominantPollutant,
})
)
const pollutants = (data.pollutants || []).map(
(pollutant: {
code: string
displayName: string
fullName: string
concentration: { value: number; units: string }
additionalInfo?: { sources: string; effects: string }
}) => ({
code: pollutant.code,
displayName: pollutant.displayName,
fullName: pollutant.fullName,
concentration: {
value: pollutant.concentration?.value || 0,
units: pollutant.concentration?.units || '',
},
additionalInfo: pollutant.additionalInfo
? {
sources: pollutant.additionalInfo.sources,
effects: pollutant.additionalInfo.effects,
}
: undefined,
})
)
const healthRecs = data.healthRecommendations
const healthRecommendations = healthRecs
? {
generalPopulation: healthRecs.generalPopulation || '',
elderly: healthRecs.elderly || '',
lungDiseasePopulation: healthRecs.lungDiseasePopulation || '',
heartDiseasePopulation: healthRecs.heartDiseasePopulation || '',
athletes: healthRecs.athletes || '',
pregnantWomen: healthRecs.pregnantWomen || '',
children: healthRecs.children || '',
}
: null
return {
success: true,
output: {
dateTime: data.dateTime || '',
regionCode: data.regionCode || '',
indexes,
pollutants,
healthRecommendations,
},
}
},
outputs: {
dateTime: {
type: 'string',
description: 'Timestamp of the air quality data',
},
regionCode: {
type: 'string',
description: 'Region code for the location',
},
indexes: {
type: 'array',
description: 'Array of air quality indexes',
items: {
type: 'object',
properties: {
code: { type: 'string', description: 'Index code (e.g., "uaqi", "usa_epa")' },
displayName: { type: 'string', description: 'Display name of the index' },
aqi: { type: 'number', description: 'Air quality index value' },
aqiDisplay: { type: 'string', description: 'Formatted AQI display string' },
color: {
type: 'object',
description: 'RGB color for the AQI level',
properties: {
red: { type: 'number' },
green: { type: 'number' },
blue: { type: 'number' },
},
},
category: {
type: 'string',
description: 'Category description (e.g., "Good", "Moderate")',
},
dominantPollutant: { type: 'string', description: 'The dominant pollutant' },
},
},
},
pollutants: {
type: 'array',
description: 'Array of pollutant concentrations',
items: {
type: 'object',
properties: {
code: { type: 'string', description: 'Pollutant code (e.g., "pm25", "o3")' },
displayName: { type: 'string', description: 'Display name' },
fullName: { type: 'string', description: 'Full pollutant name' },
concentration: {
type: 'object',
description: 'Concentration info',
properties: {
value: { type: 'number', description: 'Concentration value' },
units: { type: 'string', description: 'Units (e.g., "PARTS_PER_BILLION")' },
},
},
additionalInfo: {
type: 'object',
description: 'Additional info about sources and effects',
},
},
},
},
healthRecommendations: {
type: 'object',
description: 'Health recommendations for different populations',
},
},
}

View File

@@ -0,0 +1,259 @@
import type {
GoogleMapsDirectionsParams,
GoogleMapsDirectionsResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsDirectionsTool: ToolConfig<
GoogleMapsDirectionsParams,
GoogleMapsDirectionsResponse
> = {
id: 'google_maps_directions',
name: 'Google Maps Directions',
description: 'Get directions and route information between two locations',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
origin: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Starting location (address or lat,lng)',
},
destination: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Destination location (address or lat,lng)',
},
mode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Travel mode: driving, walking, bicycling, or transit',
},
avoid: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Features to avoid: tolls, highways, or ferries',
},
waypoints: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Array of intermediate waypoints',
},
units: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Unit system: metric or imperial',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/directions/json')
url.searchParams.set('origin', params.origin.trim())
url.searchParams.set('destination', params.destination.trim())
url.searchParams.set('key', params.apiKey.trim())
if (params.mode) {
url.searchParams.set('mode', params.mode)
}
if (params.avoid) {
url.searchParams.set('avoid', params.avoid)
}
if (params.waypoints && params.waypoints.length > 0) {
url.searchParams.set('waypoints', params.waypoints.join('|'))
}
if (params.units) {
url.searchParams.set('units', params.units)
}
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Directions request failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const routes = data.routes.map(
(route: {
summary: string
legs: Array<{
start_address: string
end_address: string
start_location: { lat: number; lng: number }
end_location: { lat: number; lng: number }
distance: { text: string; value: number }
duration: { text: string; value: number }
steps: Array<{
html_instructions: string
distance: { text: string; value: number }
duration: { text: string; value: number }
start_location: { lat: number; lng: number }
end_location: { lat: number; lng: number }
travel_mode: string
maneuver?: string
}>
}>
overview_polyline: { points: string }
warnings: string[]
waypoint_order: number[]
}) => ({
summary: route.summary,
legs: route.legs.map((leg) => ({
startAddress: leg.start_address,
endAddress: leg.end_address,
startLocation: {
lat: leg.start_location.lat,
lng: leg.start_location.lng,
},
endLocation: {
lat: leg.end_location.lat,
lng: leg.end_location.lng,
},
distanceText: leg.distance.text,
distanceMeters: leg.distance.value,
durationText: leg.duration.text,
durationSeconds: leg.duration.value,
steps: leg.steps.map((step) => ({
instruction: step.html_instructions.replace(/<[^>]*>/g, ''),
distanceText: step.distance.text,
distanceMeters: step.distance.value,
durationText: step.duration.text,
durationSeconds: step.duration.value,
startLocation: {
lat: step.start_location.lat,
lng: step.start_location.lng,
},
endLocation: {
lat: step.end_location.lat,
lng: step.end_location.lng,
},
travelMode: step.travel_mode,
maneuver: step.maneuver ?? null,
})),
})),
overviewPolyline: route.overview_polyline.points,
warnings: route.warnings ?? [],
waypointOrder: route.waypoint_order ?? [],
})
)
const primaryRoute = routes[0]
const primaryLeg = primaryRoute?.legs[0]
return {
success: true,
output: {
routes,
distanceText: primaryLeg?.distanceText ?? '',
distanceMeters: primaryLeg?.distanceMeters ?? 0,
durationText: primaryLeg?.durationText ?? '',
durationSeconds: primaryLeg?.durationSeconds ?? 0,
startAddress: primaryLeg?.startAddress ?? '',
endAddress: primaryLeg?.endAddress ?? '',
steps: primaryLeg?.steps ?? [],
polyline: primaryRoute?.overviewPolyline ?? '',
},
}
},
outputs: {
routes: {
type: 'array',
description: 'All available routes',
items: {
type: 'object',
properties: {
summary: { type: 'string', description: 'Route summary (main road names)' },
legs: { type: 'array', description: 'Route legs (segments between waypoints)' },
overviewPolyline: {
type: 'string',
description: 'Encoded polyline for the entire route',
},
warnings: { type: 'array', description: 'Route warnings' },
waypointOrder: { type: 'array', description: 'Optimized waypoint order (if requested)' },
},
},
},
distanceText: {
type: 'string',
description: 'Total distance as human-readable text (e.g., "5.2 km")',
},
distanceMeters: {
type: 'number',
description: 'Total distance in meters',
},
durationText: {
type: 'string',
description: 'Total duration as human-readable text (e.g., "15 mins")',
},
durationSeconds: {
type: 'number',
description: 'Total duration in seconds',
},
startAddress: {
type: 'string',
description: 'Resolved starting address',
},
endAddress: {
type: 'string',
description: 'Resolved ending address',
},
steps: {
type: 'array',
description: 'Turn-by-turn navigation instructions',
items: {
type: 'object',
properties: {
instruction: { type: 'string', description: 'Navigation instruction (HTML stripped)' },
distanceText: { type: 'string', description: 'Step distance as text' },
distanceMeters: { type: 'number', description: 'Step distance in meters' },
durationText: { type: 'string', description: 'Step duration as text' },
durationSeconds: { type: 'number', description: 'Step duration in seconds' },
startLocation: { type: 'object', description: 'Step start coordinates' },
endLocation: { type: 'object', description: 'Step end coordinates' },
travelMode: { type: 'string', description: 'Travel mode for this step' },
maneuver: {
type: 'string',
description: 'Maneuver type (turn-left, etc.)',
optional: true,
},
},
},
},
polyline: {
type: 'string',
description: 'Encoded polyline for the primary route',
},
},
}

View File

@@ -0,0 +1,181 @@
import type {
GoogleMapsDistanceMatrixParams,
GoogleMapsDistanceMatrixResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsDistanceMatrixTool: ToolConfig<
GoogleMapsDistanceMatrixParams,
GoogleMapsDistanceMatrixResponse
> = {
id: 'google_maps_distance_matrix',
name: 'Google Maps Distance Matrix',
description: 'Calculate travel distance and time between multiple origins and destinations',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
origin: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Origin location (address or lat,lng)',
},
destinations: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of destination locations',
},
mode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Travel mode: driving, walking, bicycling, or transit',
},
avoid: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Features to avoid: tolls, highways, or ferries',
},
units: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Unit system: metric or imperial',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/distancematrix/json')
url.searchParams.set('origins', params.origin.trim())
url.searchParams.set('destinations', params.destinations.join('|'))
url.searchParams.set('key', params.apiKey.trim())
if (params.mode) {
url.searchParams.set('mode', params.mode)
}
if (params.avoid) {
url.searchParams.set('avoid', params.avoid)
}
if (params.units) {
url.searchParams.set('units', params.units)
}
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Distance matrix request failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const rows = data.rows.map(
(row: {
elements: Array<{
distance?: { text: string; value: number }
duration?: { text: string; value: number }
duration_in_traffic?: { text: string; value: number }
status: string
}>
}) => ({
elements: row.elements.map((element) => ({
distanceText: element.distance?.text ?? 'N/A',
distanceMeters: element.distance?.value ?? 0,
durationText: element.duration?.text ?? 'N/A',
durationSeconds: element.duration?.value ?? 0,
durationInTrafficText: element.duration_in_traffic?.text ?? null,
durationInTrafficSeconds: element.duration_in_traffic?.value ?? null,
status: element.status,
})),
})
)
return {
success: true,
output: {
originAddresses: data.origin_addresses ?? [],
destinationAddresses: data.destination_addresses ?? [],
rows,
},
}
},
outputs: {
originAddresses: {
type: 'array',
description: 'Resolved origin addresses',
items: {
type: 'string',
},
},
destinationAddresses: {
type: 'array',
description: 'Resolved destination addresses',
items: {
type: 'string',
},
},
rows: {
type: 'array',
description: 'Distance matrix rows (one per origin)',
items: {
type: 'object',
properties: {
elements: {
type: 'array',
description: 'Elements (one per destination)',
items: {
type: 'object',
properties: {
distanceText: { type: 'string', description: 'Distance as text (e.g., "5.2 km")' },
distanceMeters: { type: 'number', description: 'Distance in meters' },
durationText: { type: 'string', description: 'Duration as text (e.g., "15 mins")' },
durationSeconds: { type: 'number', description: 'Duration in seconds' },
durationInTrafficText: {
type: 'string',
description: 'Duration in traffic as text',
optional: true,
},
durationInTrafficSeconds: {
type: 'number',
description: 'Duration in traffic in seconds',
optional: true,
},
status: {
type: 'string',
description: 'Element status (OK, NOT_FOUND, ZERO_RESULTS)',
},
},
},
},
},
},
},
},
}

View File

@@ -0,0 +1,91 @@
import type {
GoogleMapsElevationParams,
GoogleMapsElevationResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsElevationTool: ToolConfig<
GoogleMapsElevationParams,
GoogleMapsElevationResponse
> = {
id: 'google_maps_elevation',
name: 'Google Maps Elevation',
description: 'Get elevation data for a location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
lat: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude coordinate',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/elevation/json')
url.searchParams.set('locations', `${params.lat},${params.lng}`)
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 (data.status !== 'OK') {
throw new Error(
`Elevation request failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const result = data.results[0]
return {
success: true,
output: {
elevation: result.elevation,
lat: result.location.lat,
lng: result.location.lng,
resolution: result.resolution,
},
}
},
outputs: {
elevation: {
type: 'number',
description: 'Elevation in meters above sea level (negative for below)',
},
lat: {
type: 'number',
description: 'Latitude of the elevation sample',
},
lng: {
type: 'number',
description: 'Longitude of the elevation sample',
},
resolution: {
type: 'number',
description:
'Maximum distance between data points (meters) from which elevation was interpolated',
},
},
}

View File

@@ -0,0 +1,130 @@
import type { GoogleMapsGeocodeParams, GoogleMapsGeocodeResponse } from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsGeocodeTool: ToolConfig<GoogleMapsGeocodeParams, GoogleMapsGeocodeResponse> =
{
id: 'google_maps_geocode',
name: 'Google Maps Geocode',
description: 'Convert an address into geographic coordinates (latitude and longitude)',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
address: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The address to geocode',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
region: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Region bias as a ccTLD code (e.g., us, uk)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/geocode/json')
url.searchParams.set('address', params.address.trim())
url.searchParams.set('key', params.apiKey.trim())
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
if (params.region) {
url.searchParams.set('region', params.region.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Geocoding failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const result = data.results[0]
const location = result.geometry.location
return {
success: true,
output: {
formattedAddress: result.formatted_address,
lat: location.lat,
lng: location.lng,
location: {
lat: location.lat,
lng: location.lng,
},
placeId: result.place_id,
addressComponents: (result.address_components || []).map(
(comp: { long_name: string; short_name: string; types: string[] }) => ({
longName: comp.long_name,
shortName: comp.short_name,
types: comp.types,
})
),
locationType: result.geometry.location_type,
},
}
},
outputs: {
formattedAddress: {
type: 'string',
description: 'The formatted address string',
},
lat: {
type: 'number',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
description: 'Longitude coordinate',
},
location: {
type: 'json',
description: 'Location object with lat and lng',
},
placeId: {
type: 'string',
description: 'Google Place ID for this location',
},
addressComponents: {
type: 'array',
description: 'Detailed address components',
items: {
type: 'object',
properties: {
longName: { type: 'string', description: 'Full name of the component' },
shortName: { type: 'string', description: 'Abbreviated name' },
types: { type: 'array', description: 'Component types' },
},
},
},
locationType: {
type: 'string',
description: 'Location accuracy type (ROOFTOP, RANGE_INTERPOLATED, etc.)',
},
},
}

View File

@@ -0,0 +1,165 @@
import type {
GoogleMapsGeolocateParams,
GoogleMapsGeolocateResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsGeolocateTool: ToolConfig<
GoogleMapsGeolocateParams,
GoogleMapsGeolocateResponse
> = {
id: 'google_maps_geolocate',
name: 'Google Maps Geolocate',
description: 'Geolocate a device using WiFi access points, cell towers, or IP address',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Geolocation API enabled',
},
homeMobileCountryCode: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Home mobile country code (MCC)',
},
homeMobileNetworkCode: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Home mobile network code (MNC)',
},
radioType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Radio type: lte, gsm, cdma, wcdma, or nr',
},
carrier: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Carrier name',
},
considerIp: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to use IP address for geolocation (default: true)',
},
cellTowers: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description:
'Array of cell tower objects with cellId, locationAreaCode, mobileCountryCode, mobileNetworkCode',
},
wifiAccessPoints: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description:
'Array of WiFi access point objects with macAddress (required), signalStrength, etc.',
},
},
request: {
url: (params) => {
return `https://www.googleapis.com/geolocation/v1/geolocate?key=${params.apiKey.trim()}`
},
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => {
const body: {
homeMobileCountryCode?: number
homeMobileNetworkCode?: number
radioType?: string
carrier?: string
considerIp?: boolean
cellTowers?: Array<{
cellId: number
locationAreaCode: number
mobileCountryCode: number
mobileNetworkCode: number
age?: number
signalStrength?: number
timingAdvance?: number
}>
wifiAccessPoints?: Array<{
macAddress: string
signalStrength?: number
age?: number
channel?: number
signalToNoiseRatio?: number
}>
} = {}
if (params.homeMobileCountryCode !== undefined) {
body.homeMobileCountryCode = params.homeMobileCountryCode
}
if (params.homeMobileNetworkCode !== undefined) {
body.homeMobileNetworkCode = params.homeMobileNetworkCode
}
if (params.radioType) {
body.radioType = params.radioType
}
if (params.carrier) {
body.carrier = params.carrier
}
if (params.considerIp !== undefined) {
body.considerIp = params.considerIp
}
if (params.cellTowers && params.cellTowers.length > 0) {
body.cellTowers = params.cellTowers
}
if (params.wifiAccessPoints && params.wifiAccessPoints.length > 0) {
body.wifiAccessPoints = params.wifiAccessPoints
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.error) {
throw new Error(`Geolocation failed: ${data.error.message || 'Unknown error'}`)
}
return {
success: true,
output: {
lat: data.location?.lat || 0,
lng: data.location?.lng || 0,
accuracy: data.accuracy || 0,
},
}
},
outputs: {
lat: {
type: 'number',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
description: 'Longitude coordinate',
},
accuracy: {
type: 'number',
description: 'Accuracy radius in meters',
},
},
}

View File

@@ -0,0 +1,32 @@
import { googleMapsAirQualityTool } from '@/tools/google_maps/air_quality'
import { googleMapsDirectionsTool } from '@/tools/google_maps/directions'
import { googleMapsDistanceMatrixTool } from '@/tools/google_maps/distance_matrix'
import { googleMapsElevationTool } from '@/tools/google_maps/elevation'
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 { googleMapsReverseGeocodeTool } from '@/tools/google_maps/reverse_geocode'
import { googleMapsSnapToRoadsTool } from '@/tools/google_maps/snap_to_roads'
import { googleMapsSpeedLimitsTool } from '@/tools/google_maps/speed_limits'
import { googleMapsTimezoneTool } from '@/tools/google_maps/timezone'
import { googleMapsValidateAddressTool } from '@/tools/google_maps/validate_address'
export {
googleMapsAirQualityTool,
googleMapsDirectionsTool,
googleMapsDistanceMatrixTool,
googleMapsElevationTool,
googleMapsGeocodeTool,
googleMapsGeolocateTool,
googleMapsPlaceDetailsTool,
googleMapsPlacesSearchTool,
googleMapsReverseGeocodeTool,
googleMapsSnapToRoadsTool,
googleMapsSpeedLimitsTool,
googleMapsTimezoneTool,
googleMapsValidateAddressTool,
}
// Export types
export * from './types'

View File

@@ -0,0 +1,274 @@
import type {
GoogleMapsPlaceDetailsParams,
GoogleMapsPlaceDetailsResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsPlaceDetailsTool: ToolConfig<
GoogleMapsPlaceDetailsParams,
GoogleMapsPlaceDetailsResponse
> = {
id: 'google_maps_place_details',
name: 'Google Maps Place Details',
description: 'Get detailed information about a specific place',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
placeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Google Place ID',
},
fields: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of fields to return',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/place/details/json')
url.searchParams.set('place_id', params.placeId.trim())
url.searchParams.set('key', params.apiKey.trim())
// Default fields if not specified - comprehensive list
const fields =
params.fields ||
'place_id,name,formatted_address,geometry,types,rating,user_ratings_total,price_level,website,formatted_phone_number,international_phone_number,opening_hours,reviews,photos,url,utc_offset,vicinity,business_status'
url.searchParams.set('fields', fields)
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Place details request failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const place = data.result
const reviews = (place.reviews || []).map(
(review: {
author_name: string
author_url?: string
profile_photo_url?: string
rating: number
text: string
time: number
relative_time_description: string
}) => ({
authorName: review.author_name,
authorUrl: review.author_url ?? null,
profilePhotoUrl: review.profile_photo_url ?? null,
rating: review.rating,
text: review.text,
time: review.time,
relativeTimeDescription: review.relative_time_description,
})
)
const photos = (place.photos || []).map(
(photo: {
photo_reference: string
height: number
width: number
html_attributions: string[]
}) => ({
photoReference: photo.photo_reference,
height: photo.height,
width: photo.width,
htmlAttributions: photo.html_attributions ?? [],
})
)
// Destructure opening hours
const openNow = place.opening_hours?.open_now ?? null
const weekdayText = place.opening_hours?.weekday_text ?? []
// Extract location
const lat = place.geometry?.location?.lat ?? null
const lng = place.geometry?.location?.lng ?? null
return {
success: true,
output: {
placeId: place.place_id,
name: place.name ?? null,
formattedAddress: place.formatted_address ?? null,
lat,
lng,
types: place.types ?? [],
rating: place.rating ?? null,
userRatingsTotal: place.user_ratings_total ?? null,
priceLevel: place.price_level ?? null,
website: place.website ?? null,
phoneNumber: place.formatted_phone_number ?? null,
internationalPhoneNumber: place.international_phone_number ?? null,
openNow,
weekdayText,
reviews,
photos,
url: place.url ?? null,
utcOffset: place.utc_offset ?? null,
vicinity: place.vicinity ?? null,
businessStatus: place.business_status ?? null,
},
}
},
outputs: {
placeId: {
type: 'string',
description: 'Google Place ID',
},
name: {
type: 'string',
description: 'Place name',
optional: true,
},
formattedAddress: {
type: 'string',
description: 'Formatted street address',
optional: true,
},
lat: {
type: 'number',
description: 'Latitude coordinate',
optional: true,
},
lng: {
type: 'number',
description: 'Longitude coordinate',
optional: true,
},
types: {
type: 'array',
description: 'Place types (e.g., restaurant, cafe)',
items: {
type: 'string',
},
},
rating: {
type: 'number',
description: 'Average rating (1.0 to 5.0)',
optional: true,
},
userRatingsTotal: {
type: 'number',
description: 'Total number of user ratings',
optional: true,
},
priceLevel: {
type: 'number',
description: 'Price level (0=Free, 1=Inexpensive, 2=Moderate, 3=Expensive, 4=Very Expensive)',
optional: true,
},
website: {
type: 'string',
description: 'Place website URL',
optional: true,
},
phoneNumber: {
type: 'string',
description: 'Local formatted phone number',
optional: true,
},
internationalPhoneNumber: {
type: 'string',
description: 'International formatted phone number',
optional: true,
},
openNow: {
type: 'boolean',
description: 'Whether the place is currently open',
optional: true,
},
weekdayText: {
type: 'array',
description: 'Opening hours formatted by day of week',
items: {
type: 'string',
},
},
reviews: {
type: 'array',
description: 'User reviews (up to 5 most relevant)',
items: {
type: 'object',
properties: {
authorName: { type: 'string', description: 'Reviewer name' },
authorUrl: { type: 'string', description: 'Reviewer profile URL', optional: true },
profilePhotoUrl: { type: 'string', description: 'Reviewer photo URL', optional: true },
rating: { type: 'number', description: 'Rating given (1-5)' },
text: { type: 'string', description: 'Review text' },
time: { type: 'number', description: 'Review timestamp (Unix epoch)' },
relativeTimeDescription: {
type: 'string',
description: 'Relative time (e.g., "a month ago")',
},
},
},
},
photos: {
type: 'array',
description: 'Place photos',
items: {
type: 'object',
properties: {
photoReference: { type: 'string', description: 'Photo reference for Place Photos API' },
height: { type: 'number', description: 'Photo height in pixels' },
width: { type: 'number', description: 'Photo width in pixels' },
htmlAttributions: { type: 'array', description: 'Required attributions' },
},
},
},
url: {
type: 'string',
description: 'Google Maps URL for the place',
optional: true,
},
utcOffset: {
type: 'number',
description: 'UTC offset in minutes',
optional: true,
},
vicinity: {
type: 'string',
description: 'Simplified address (neighborhood/street)',
optional: true,
},
businessStatus: {
type: 'string',
description: 'Business status (OPERATIONAL, CLOSED_TEMPORARILY, CLOSED_PERMANENTLY)',
optional: true,
},
},
}

View File

@@ -0,0 +1,170 @@
import type {
GoogleMapsPlacesSearchParams,
GoogleMapsPlacesSearchResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsPlacesSearchTool: ToolConfig<
GoogleMapsPlacesSearchParams,
GoogleMapsPlacesSearchResponse
> = {
id: 'google_maps_places_search',
name: 'Google Maps Places Search',
description: 'Search for places using a text query',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Search query (e.g., "restaurants in Times Square")',
},
location: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Location to bias results towards ({lat, lng})',
},
radius: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Search radius in meters',
},
type: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Place type filter (e.g., restaurant, cafe, hotel)',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
region: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Region bias as a ccTLD code (e.g., us, uk)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/place/textsearch/json')
url.searchParams.set('query', params.query.trim())
url.searchParams.set('key', params.apiKey.trim())
if (params.location) {
url.searchParams.set('location', `${params.location.lat},${params.location.lng}`)
}
if (params.radius) {
url.searchParams.set('radius', params.radius.toString())
}
if (params.type) {
url.searchParams.set('type', params.type)
}
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
if (params.region) {
url.searchParams.set('region', params.region.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK' && data.status !== 'ZERO_RESULTS') {
throw new Error(
`Places search failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const places = (data.results || []).map(
(place: {
place_id: string
name: string
formatted_address: string
geometry: { location: { lat: number; lng: number } }
types: string[]
rating?: number
user_ratings_total?: number
price_level?: number
opening_hours?: { open_now: boolean }
photos?: Array<{ photo_reference: string; height: number; width: number }>
business_status?: string
}) => ({
placeId: place.place_id,
name: place.name,
formattedAddress: place.formatted_address,
lat: place.geometry.location.lat,
lng: place.geometry.location.lng,
types: place.types ?? [],
rating: place.rating ?? null,
userRatingsTotal: place.user_ratings_total ?? null,
priceLevel: place.price_level ?? null,
openNow: place.opening_hours?.open_now ?? null,
photoReference: place.photos?.[0]?.photo_reference ?? null,
businessStatus: place.business_status ?? null,
})
)
return {
success: true,
output: {
places,
nextPageToken: data.next_page_token ?? null,
},
}
},
outputs: {
places: {
type: 'array',
description: 'List of places found',
items: {
type: 'object',
properties: {
placeId: { type: 'string', description: 'Google Place ID' },
name: { type: 'string', description: 'Place name' },
formattedAddress: { type: 'string', description: 'Formatted address' },
lat: { type: 'number', description: 'Latitude' },
lng: { type: 'number', description: 'Longitude' },
types: { type: 'array', description: 'Place types' },
rating: { type: 'number', description: 'Average rating (1-5)', optional: true },
userRatingsTotal: { type: 'number', description: 'Number of ratings', optional: true },
priceLevel: { type: 'number', description: 'Price level (0-4)', optional: true },
openNow: { type: 'boolean', description: 'Whether currently open', optional: true },
photoReference: {
type: 'string',
description: 'Photo reference for Photos API',
optional: true,
},
businessStatus: { type: 'string', description: 'Business status', optional: true },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -0,0 +1,117 @@
import type {
GoogleMapsReverseGeocodeParams,
GoogleMapsReverseGeocodeResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsReverseGeocodeTool: ToolConfig<
GoogleMapsReverseGeocodeParams,
GoogleMapsReverseGeocodeResponse
> = {
id: 'google_maps_reverse_geocode',
name: 'Google Maps Reverse Geocode',
description:
'Convert geographic coordinates (latitude and longitude) into a human-readable address',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
lat: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude coordinate',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for results (e.g., en, es, fr)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/geocode/json')
url.searchParams.set('latlng', `${params.lat},${params.lng}`)
url.searchParams.set('key', params.apiKey.trim())
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Reverse geocoding failed: ${data.status} - ${data.error_message || 'Unknown error'}`
)
}
const result = data.results[0]
return {
success: true,
output: {
formattedAddress: result.formatted_address,
placeId: result.place_id,
addressComponents: (result.address_components || []).map(
(comp: { long_name: string; short_name: string; types: string[] }) => ({
longName: comp.long_name,
shortName: comp.short_name,
types: comp.types,
})
),
types: result.types || [],
},
}
},
outputs: {
formattedAddress: {
type: 'string',
description: 'The formatted address string',
},
placeId: {
type: 'string',
description: 'Google Place ID for this location',
},
addressComponents: {
type: 'array',
description: 'Detailed address components',
items: {
type: 'object',
properties: {
longName: { type: 'string', description: 'Full name of the component' },
shortName: { type: 'string', description: 'Abbreviated name' },
types: { type: 'array', description: 'Component types' },
},
},
},
types: {
type: 'array',
description: 'Address types (e.g., street_address, route)',
items: {
type: 'string',
},
},
},
}

View File

@@ -0,0 +1,113 @@
import type {
GoogleMapsSnapToRoadsParams,
GoogleMapsSnapToRoadsResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsSnapToRoadsTool: ToolConfig<
GoogleMapsSnapToRoadsParams,
GoogleMapsSnapToRoadsResponse
> = {
id: 'google_maps_snap_to_roads',
name: 'Google Maps Snap to Roads',
description: 'Snap GPS coordinates to the nearest road segment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Roads API enabled',
},
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Pipe-separated list of lat,lng coordinates (e.g., "60.170880,24.942795|60.170879,24.942796")',
},
interpolate: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to interpolate additional points along the road',
},
},
request: {
url: (params) => {
const url = new URL('https://roads.googleapis.com/v1/snapToRoads')
url.searchParams.set('path', params.path.trim())
url.searchParams.set('key', params.apiKey.trim())
if (params.interpolate !== undefined) {
url.searchParams.set('interpolate', String(params.interpolate))
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.error) {
throw new Error(`Snap to Roads failed: ${data.error.message || 'Unknown error'}`)
}
const snappedPoints = (data.snappedPoints || []).map(
(point: {
location: { latitude: number; longitude: number }
originalIndex?: number
placeId: string
}) => ({
location: {
lat: point.location.latitude,
lng: point.location.longitude,
},
originalIndex: point.originalIndex,
placeId: point.placeId,
})
)
return {
success: true,
output: {
snappedPoints,
warningMessage: data.warningMessage || null,
},
}
},
outputs: {
snappedPoints: {
type: 'array',
description: 'Array of snapped points on roads',
items: {
type: 'object',
properties: {
location: {
type: 'object',
description: 'Snapped location coordinates',
properties: {
lat: { type: 'number', description: 'Latitude' },
lng: { type: 'number', description: 'Longitude' },
},
},
originalIndex: {
type: 'number',
description: 'Index in the original path (if not interpolated)',
},
placeId: { type: 'string', description: 'Place ID for this road segment' },
},
},
},
warningMessage: {
type: 'string',
description: 'Warning message if any (e.g., if points could not be snapped)',
},
},
}

View File

@@ -0,0 +1,133 @@
import type {
GoogleMapsSpeedLimitsParams,
GoogleMapsSpeedLimitsResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsSpeedLimitsTool: ToolConfig<
GoogleMapsSpeedLimitsParams,
GoogleMapsSpeedLimitsResponse
> = {
id: 'google_maps_speed_limits',
name: 'Google Maps Speed Limits',
description: 'Get speed limits for road segments',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Roads API enabled',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Pipe-separated list of lat,lng coordinates (e.g., "60.170880,24.942795|60.170879,24.942796")',
},
placeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of Place IDs for road segments (alternative to path)',
},
},
request: {
url: (params) => {
const url = new URL('https://roads.googleapis.com/v1/speedLimits')
url.searchParams.set('key', params.apiKey.trim())
if (params.path) {
url.searchParams.set('path', params.path.trim())
}
if (params.placeIds && params.placeIds.length > 0) {
for (const placeId of params.placeIds) {
url.searchParams.append('placeId', placeId.trim())
}
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.error) {
throw new Error(`Speed Limits failed: ${data.error.message || 'Unknown error'}`)
}
const speedLimits = (data.speedLimits || []).map(
(limit: { placeId: string; speedLimit: number; units: 'KPH' | 'MPH' }) => ({
placeId: limit.placeId,
speedLimit: limit.speedLimit,
units: limit.units,
})
)
const snappedPoints = (data.snappedPoints || []).map(
(point: {
location: { latitude: number; longitude: number }
originalIndex?: number
placeId: string
}) => ({
location: {
lat: point.location.latitude,
lng: point.location.longitude,
},
originalIndex: point.originalIndex,
placeId: point.placeId,
})
)
return {
success: true,
output: {
speedLimits,
snappedPoints,
},
}
},
outputs: {
speedLimits: {
type: 'array',
description: 'Array of speed limits for road segments',
items: {
type: 'object',
properties: {
placeId: { type: 'string', description: 'Place ID for the road segment' },
speedLimit: { type: 'number', description: 'Speed limit value' },
units: { type: 'string', description: 'Speed limit units (KPH or MPH)' },
},
},
},
snappedPoints: {
type: 'array',
description: 'Array of snapped points corresponding to the speed limits',
items: {
type: 'object',
properties: {
location: {
type: 'object',
description: 'Snapped location coordinates',
properties: {
lat: { type: 'number', description: 'Latitude' },
lng: { type: 'number', description: 'Longitude' },
},
},
originalIndex: { type: 'number', description: 'Index in the original path' },
placeId: { type: 'string', description: 'Place ID for this road segment' },
},
},
},
},
}

View File

@@ -0,0 +1,120 @@
import type {
GoogleMapsTimezoneParams,
GoogleMapsTimezoneResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsTimezoneTool: ToolConfig<
GoogleMapsTimezoneParams,
GoogleMapsTimezoneResponse
> = {
id: 'google_maps_timezone',
name: 'Google Maps Timezone',
description: 'Get timezone information for a location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key',
},
lat: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude coordinate',
},
timestamp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp to determine DST offset (defaults to current time)',
},
language: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for timezone name (e.g., en, es, fr)',
},
},
request: {
url: (params) => {
const url = new URL('https://maps.googleapis.com/maps/api/timezone/json')
url.searchParams.set('location', `${params.lat},${params.lng}`)
// Use provided timestamp or current time
const timestamp = params.timestamp ?? Math.floor(Date.now() / 1000)
url.searchParams.set('timestamp', timestamp.toString())
url.searchParams.set('key', params.apiKey.trim())
if (params.language) {
url.searchParams.set('language', params.language.trim())
}
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.status !== 'OK') {
throw new Error(
`Timezone request failed: ${data.status} - ${data.errorMessage || 'Unknown error'}`
)
}
// Calculate total offset
const totalOffsetSeconds = data.rawOffset + data.dstOffset
const totalOffsetHours = totalOffsetSeconds / 3600
return {
success: true,
output: {
timeZoneId: data.timeZoneId,
timeZoneName: data.timeZoneName,
rawOffset: data.rawOffset,
dstOffset: data.dstOffset,
totalOffsetSeconds,
totalOffsetHours,
},
}
},
outputs: {
timeZoneId: {
type: 'string',
description: 'IANA timezone ID (e.g., "America/New_York", "Europe/London")',
},
timeZoneName: {
type: 'string',
description: 'Localized timezone name (e.g., "Eastern Daylight Time")',
},
rawOffset: {
type: 'number',
description: 'UTC offset in seconds (without DST)',
},
dstOffset: {
type: 'number',
description: 'Daylight Saving Time offset in seconds (0 if not in DST)',
},
totalOffsetSeconds: {
type: 'number',
description: 'Total UTC offset in seconds (rawOffset + dstOffset)',
},
totalOffsetHours: {
type: 'number',
description: 'Total UTC offset in hours (e.g., -5 for EST, -4 for EDT)',
},
},
}

View File

@@ -0,0 +1,481 @@
import type { ToolResponse } from '@/tools/types'
/**
* Common location type
*/
export interface LatLng {
lat: number
lng: number
}
/**
* Address component from geocoding
*/
export interface AddressComponent {
longName: string
shortName: string
types: string[]
}
/**
* Snapped point from Roads API
*/
export interface SnappedPoint {
location: LatLng
originalIndex?: number
placeId: string
}
/**
* Speed limit info from Roads API
*/
export interface SpeedLimit {
placeId: string
speedLimit: number
units: 'KPH' | 'MPH'
}
/**
* Cell tower info for geolocation
*/
export interface CellTower {
cellId: number
locationAreaCode: number
mobileCountryCode: number
mobileNetworkCode: number
age?: number
signalStrength?: number
timingAdvance?: number
}
/**
* WiFi access point info for geolocation
*/
export interface WifiAccessPoint {
macAddress: string
signalStrength?: number
age?: number
channel?: number
signalToNoiseRatio?: number
}
/**
* Air quality index info
*/
export interface AirQualityIndex {
code: string
displayName: string
aqi: number
aqiDisplay: string
color: {
red: number
green: number
blue: number
}
category: string
dominantPollutant: string
}
/**
* Pollutant concentration info
*/
export interface Pollutant {
code: string
displayName: string
fullName: string
concentration: {
value: number
units: string
}
additionalInfo?: {
sources: string
effects: string
}
}
// Geocode
// ============================================================================
export interface GoogleMapsGeocodeParams {
apiKey: string
address: string
language?: string
region?: string
}
export interface GoogleMapsGeocodeResponse extends ToolResponse {
output: {
formattedAddress: string
lat: number
lng: number
location: LatLng
placeId: string
addressComponents: AddressComponent[]
locationType: string
}
}
// ============================================================================
// Reverse Geocode
// ============================================================================
export interface GoogleMapsReverseGeocodeParams {
apiKey: string
lat: number
lng: number
language?: string
}
export interface GoogleMapsReverseGeocodeResponse extends ToolResponse {
output: {
formattedAddress: string
placeId: string
addressComponents: AddressComponent[]
types: string[]
}
}
// ============================================================================
// Directions
// ============================================================================
export interface DirectionsStep {
instruction: string
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
startLocation: LatLng
endLocation: LatLng
travelMode: string
maneuver: string | null
}
export interface DirectionsLeg {
startAddress: string
endAddress: string
startLocation: LatLng
endLocation: LatLng
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
steps: DirectionsStep[]
}
export interface DirectionsRoute {
summary: string
legs: DirectionsLeg[]
overviewPolyline: string
warnings: string[]
waypointOrder: number[]
}
export interface GoogleMapsDirectionsParams {
apiKey: string
origin: string
destination: string
mode?: 'driving' | 'walking' | 'bicycling' | 'transit'
avoid?: string
waypoints?: string[]
units?: 'metric' | 'imperial'
language?: string
}
export interface GoogleMapsDirectionsResponse extends ToolResponse {
output: {
routes: DirectionsRoute[]
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
startAddress: string
endAddress: string
steps: DirectionsStep[]
polyline: string
}
}
// ============================================================================
// Distance Matrix
// ============================================================================
export interface DistanceMatrixElement {
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
durationInTrafficText: string | null
durationInTrafficSeconds: number | null
status: string
}
export interface DistanceMatrixRow {
elements: DistanceMatrixElement[]
}
export interface GoogleMapsDistanceMatrixParams {
apiKey: string
origin: string
destinations: string[]
mode?: 'driving' | 'walking' | 'bicycling' | 'transit'
avoid?: string
units?: 'metric' | 'imperial'
language?: string
}
export interface GoogleMapsDistanceMatrixResponse extends ToolResponse {
output: {
originAddresses: string[]
destinationAddresses: string[]
rows: DistanceMatrixRow[]
}
}
// ============================================================================
// Places Search
// ============================================================================
export interface PlaceResult {
placeId: string
name: string
formattedAddress: string
lat: number
lng: number
types: string[]
rating: number | null
userRatingsTotal: number | null
priceLevel: number | null
openNow: boolean | null
photoReference: string | null
businessStatus: string | null
}
export interface GoogleMapsPlacesSearchParams {
apiKey: string
query: string
location?: LatLng
radius?: number
type?: string
language?: string
region?: string
}
export interface GoogleMapsPlacesSearchResponse extends ToolResponse {
output: {
places: PlaceResult[]
nextPageToken: string | null
}
}
// ============================================================================
// Place Details
// ============================================================================
export interface PlaceReview {
authorName: string
authorUrl: string | null
profilePhotoUrl: string | null
rating: number
text: string
time: number
relativeTimeDescription: string
}
export interface PlacePhoto {
photoReference: string
height: number
width: number
htmlAttributions: string[]
}
export interface GoogleMapsPlaceDetailsParams {
apiKey: string
placeId: string
fields?: string
language?: string
}
export interface GoogleMapsPlaceDetailsResponse extends ToolResponse {
output: {
placeId: string
name: string | null
formattedAddress: string | null
lat: number | null
lng: number | null
types: string[]
rating: number | null
userRatingsTotal: number | null
priceLevel: number | null
website: string | null
phoneNumber: string | null
internationalPhoneNumber: string | null
openNow: boolean | null
weekdayText: string[]
reviews: PlaceReview[]
photos: PlacePhoto[]
url: string | null
utcOffset: number | null
vicinity: string | null
businessStatus: string | null
}
}
// ============================================================================
// Elevation
// ============================================================================
export interface GoogleMapsElevationParams {
apiKey: string
lat: number
lng: number
}
export interface GoogleMapsElevationResponse extends ToolResponse {
output: {
elevation: number
lat: number
lng: number
resolution: number
}
}
// ============================================================================
// Timezone
// ============================================================================
export interface GoogleMapsTimezoneParams {
apiKey: string
lat: number
lng: number
timestamp?: number
language?: string
}
export interface GoogleMapsTimezoneResponse extends ToolResponse {
output: {
timeZoneId: string
timeZoneName: string
rawOffset: number
dstOffset: number
totalOffsetSeconds: number
totalOffsetHours: number
}
}
// ============================================================================
// Snap to Roads
// ============================================================================
export interface GoogleMapsSnapToRoadsParams {
apiKey: string
path: string
interpolate?: boolean
}
export interface GoogleMapsSnapToRoadsResponse extends ToolResponse {
output: {
snappedPoints: SnappedPoint[]
warningMessage: string | null
}
}
// ============================================================================
// Speed Limits
// ============================================================================
export interface GoogleMapsSpeedLimitsParams {
apiKey: string
path?: string
placeIds?: string[]
}
export interface GoogleMapsSpeedLimitsResponse extends ToolResponse {
output: {
speedLimits: SpeedLimit[]
snappedPoints: SnappedPoint[]
}
}
// ============================================================================
// Validate Address
// ============================================================================
export interface GoogleMapsValidateAddressParams {
apiKey: string
address: string
regionCode?: string
locality?: string
enableUspsCass?: boolean
}
export interface GoogleMapsValidateAddressResponse extends ToolResponse {
output: {
formattedAddress: string
lat: number
lng: number
placeId: string
addressComplete: boolean
hasUnconfirmedComponents: boolean
hasInferredComponents: boolean
hasReplacedComponents: boolean
validationGranularity: string
geocodeGranularity: string
addressComponents: AddressComponent[]
missingComponentTypes: string[]
unconfirmedComponentTypes: string[]
unresolvedTokens: string[]
}
}
// ============================================================================
// Geolocate
// ============================================================================
export interface GoogleMapsGeolocateParams {
apiKey: string
homeMobileCountryCode?: number
homeMobileNetworkCode?: number
radioType?: 'lte' | 'gsm' | 'cdma' | 'wcdma' | 'nr'
carrier?: string
considerIp?: boolean
cellTowers?: CellTower[]
wifiAccessPoints?: WifiAccessPoint[]
}
export interface GoogleMapsGeolocateResponse extends ToolResponse {
output: {
lat: number
lng: number
accuracy: number
}
}
// ============================================================================
// Air Quality
// ============================================================================
export interface GoogleMapsAirQualityParams {
apiKey: string
lat: number
lng: number
languageCode?: string
}
export interface GoogleMapsAirQualityResponse extends ToolResponse {
output: {
dateTime: string
regionCode: string
indexes: AirQualityIndex[]
pollutants: Pollutant[]
healthRecommendations: {
generalPopulation: string
elderly: string
lungDiseasePopulation: string
heartDiseasePopulation: string
athletes: string
pregnantWomen: string
children: string
} | null
}
}

View File

@@ -0,0 +1,194 @@
import type {
GoogleMapsValidateAddressParams,
GoogleMapsValidateAddressResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsValidateAddressTool: ToolConfig<
GoogleMapsValidateAddressParams,
GoogleMapsValidateAddressResponse
> = {
id: 'google_maps_validate_address',
name: 'Google Maps Validate Address',
description: 'Validate and standardize a postal address',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Address Validation API enabled',
},
address: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The address to validate (as a single string)',
},
regionCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ISO 3166-1 alpha-2 country code (e.g., "US", "CA")',
},
locality: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'City or locality name',
},
enableUspsCass: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable USPS CASS validation for US addresses',
},
},
request: {
url: (params) => {
return `https://addressvalidation.googleapis.com/v1:validateAddress?key=${params.apiKey.trim()}`
},
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => {
const body: {
address: { addressLines: string[]; regionCode?: string; locality?: string }
enableUspsCass?: boolean
} = {
address: {
addressLines: [params.address.trim()],
},
}
if (params.regionCode) {
body.address.regionCode = params.regionCode.trim()
}
if (params.locality) {
body.address.locality = params.locality.trim()
}
if (params.enableUspsCass !== undefined) {
body.enableUspsCass = params.enableUspsCass
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.error) {
throw new Error(`Address Validation failed: ${data.error.message || 'Unknown error'}`)
}
const result = data.result
const verdict = result?.verdict || {}
const address = result?.address || {}
const geocode = result?.geocode || {}
const addressComponents = (address.addressComponents || []).map(
(comp: {
componentName: { text: string; languageCode?: string }
componentType: string
confirmationLevel: string
}) => ({
longName: comp.componentName?.text || '',
shortName: comp.componentName?.text || '',
types: [comp.componentType],
})
)
return {
success: true,
output: {
formattedAddress: address.formattedAddress || '',
lat: geocode.location?.latitude || 0,
lng: geocode.location?.longitude || 0,
placeId: geocode.placeId || '',
addressComplete: verdict.addressComplete || false,
hasUnconfirmedComponents: verdict.hasUnconfirmedComponents || false,
hasInferredComponents: verdict.hasInferredComponents || false,
hasReplacedComponents: verdict.hasReplacedComponents || false,
validationGranularity: verdict.validationGranularity || '',
geocodeGranularity: verdict.geocodeGranularity || '',
addressComponents,
missingComponentTypes: address.missingComponentTypes || [],
unconfirmedComponentTypes: address.unconfirmedComponentTypes || [],
unresolvedTokens: address.unresolvedTokens || [],
},
}
},
outputs: {
formattedAddress: {
type: 'string',
description: 'The standardized formatted address',
},
lat: {
type: 'number',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
description: 'Longitude coordinate',
},
placeId: {
type: 'string',
description: 'Google Place ID for this address',
},
addressComplete: {
type: 'boolean',
description: 'Whether the address is complete and deliverable',
},
hasUnconfirmedComponents: {
type: 'boolean',
description: 'Whether some address components could not be confirmed',
},
hasInferredComponents: {
type: 'boolean',
description: 'Whether some components were inferred (not in input)',
},
hasReplacedComponents: {
type: 'boolean',
description: 'Whether some components were replaced with canonical values',
},
validationGranularity: {
type: 'string',
description: 'Granularity of validation (PREMISE, SUB_PREMISE, ROUTE, etc.)',
},
geocodeGranularity: {
type: 'string',
description: 'Granularity of the geocode result',
},
addressComponents: {
type: 'array',
description: 'Detailed address components',
items: {
type: 'object',
properties: {
longName: { type: 'string', description: 'Full name of the component' },
shortName: { type: 'string', description: 'Abbreviated name' },
types: { type: 'array', description: 'Component types' },
},
},
},
missingComponentTypes: {
type: 'array',
description: 'Types of address components that are missing',
},
unconfirmedComponentTypes: {
type: 'array',
description: 'Types of components that could not be confirmed',
},
unresolvedTokens: {
type: 'array',
description: 'Input tokens that could not be resolved',
},
},
}

View File

@@ -205,6 +205,7 @@ import {
dropboxSearchTool,
dropboxUploadTool,
} from '@/tools/dropbox'
import { chainOfThoughtTool, predictTool, reactTool } from '@/tools/dspy'
import { duckduckgoSearchTool } from '@/tools/duckduckgo'
import {
dynamodbDeleteTool,
@@ -545,6 +546,21 @@ import {
googleGroupsUpdateMemberTool,
googleGroupsUpdateSettingsTool,
} from '@/tools/google_groups'
import {
googleMapsAirQualityTool,
googleMapsDirectionsTool,
googleMapsDistanceMatrixTool,
googleMapsElevationTool,
googleMapsGeocodeTool,
googleMapsGeolocateTool,
googleMapsPlaceDetailsTool,
googleMapsPlacesSearchTool,
googleMapsReverseGeocodeTool,
googleMapsSnapToRoadsTool,
googleMapsSpeedLimitsTool,
googleMapsTimezoneTool,
googleMapsValidateAddressTool,
} from '@/tools/google_maps'
import {
googleSheetsAppendTool,
googleSheetsAppendV2Tool,
@@ -2144,6 +2160,9 @@ export const tools: Record<string, ToolConfig> = {
dropbox_create_shared_link: dropboxCreateSharedLinkTool,
dropbox_search: dropboxSearchTool,
duckduckgo_search: duckduckgoSearchTool,
dspy_predict: predictTool,
dspy_chain_of_thought: chainOfThoughtTool,
dspy_react: reactTool,
mongodb_query: mongodbQueryTool,
mongodb_insert: mongodbInsertTool,
mongodb_update: mongodbUpdateTool,
@@ -2427,6 +2446,19 @@ export const tools: Record<string, ToolConfig> = {
google_docs_read: googleDocsReadTool,
google_docs_write: googleDocsWriteTool,
google_docs_create: googleDocsCreateTool,
google_maps_air_quality: googleMapsAirQualityTool,
google_maps_directions: googleMapsDirectionsTool,
google_maps_distance_matrix: googleMapsDistanceMatrixTool,
google_maps_elevation: googleMapsElevationTool,
google_maps_geocode: googleMapsGeocodeTool,
google_maps_geolocate: googleMapsGeolocateTool,
google_maps_place_details: googleMapsPlaceDetailsTool,
google_maps_places_search: googleMapsPlacesSearchTool,
google_maps_reverse_geocode: googleMapsReverseGeocodeTool,
google_maps_snap_to_roads: googleMapsSnapToRoadsTool,
google_maps_speed_limits: googleMapsSpeedLimitsTool,
google_maps_timezone: googleMapsTimezoneTool,
google_maps_validate_address: googleMapsValidateAddressTool,
google_sheets_read: googleSheetsReadTool,
google_sheets_write: googleSheetsWriteTool,
google_sheets_update: googleSheetsUpdateTool,