chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
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")',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
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',
},
},
}
+273
View File
@@ -0,0 +1,273 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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',
},
},
}
@@ -0,0 +1,195 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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)',
},
},
},
},
},
},
},
},
}
+106
View File
@@ -0,0 +1,106 @@
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',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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 ?? null,
},
}
},
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',
optional: true,
},
},
}
+144
View File
@@ -0,0 +1,144 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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.)',
},
},
}
+179
View File
@@ -0,0 +1,179 @@
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.',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
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',
},
},
}
+38
View File
@@ -0,0 +1,38 @@
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 { googleMapsPlacesNearbyTool } from '@/tools/google_maps/places_nearby'
import { googleMapsPlacesSearchTool } from '@/tools/google_maps/places_search'
import { googleMapsPollenTool } from '@/tools/google_maps/pollen'
import { googleMapsReverseGeocodeTool } from '@/tools/google_maps/reverse_geocode'
import { googleMapsSnapToRoadsTool } from '@/tools/google_maps/snap_to_roads'
import { googleMapsSolarTool } from '@/tools/google_maps/solar'
import { googleMapsSpeedLimitsTool } from '@/tools/google_maps/speed_limits'
import { googleMapsTimezoneTool } from '@/tools/google_maps/timezone'
import { googleMapsValidateAddressTool } from '@/tools/google_maps/validate_address'
export {
googleMapsAirQualityTool,
googleMapsDirectionsTool,
googleMapsDistanceMatrixTool,
googleMapsElevationTool,
googleMapsGeocodeTool,
googleMapsGeolocateTool,
googleMapsPlaceDetailsTool,
googleMapsPlacesNearbyTool,
googleMapsPlacesSearchTool,
googleMapsPollenTool,
googleMapsReverseGeocodeTool,
googleMapsSnapToRoadsTool,
googleMapsSolarTool,
googleMapsSpeedLimitsTool,
googleMapsTimezoneTool,
googleMapsValidateAddressTool,
}
// Export types
export * from './types'
+288
View File
@@ -0,0 +1,288 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.017,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
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,
},
},
}
+203
View File
@@ -0,0 +1,203 @@
import type {
GoogleMapsPlacesNearbyParams,
GoogleMapsPlacesNearbyResponse,
} from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsPlacesNearbyTool: ToolConfig<
GoogleMapsPlacesNearbyParams,
GoogleMapsPlacesNearbyResponse
> = {
id: 'google_maps_places_nearby',
name: 'Google Maps Places Nearby Search',
description: 'Search for places of a given type within a radius of 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 of the center point to search around',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude of the center point to search around',
},
radius: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Search radius in meters (up to 50000)',
},
includedTypes: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Place types to include in the results (e.g., restaurant, cafe)',
},
maxResultCount: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return (1-20, defaults to 20)',
},
rankPreference: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'How to rank results: POPULARITY (default) or DISTANCE',
},
languageCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for the response (e.g., en, es)',
},
regionCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Region bias as a ccTLD code (e.g., us, uk)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.032,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: () => 'https://places.googleapis.com/v1/places:searchNearby',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'X-Goog-Api-Key': params.apiKey.trim(),
'X-Goog-FieldMask':
'places.id,places.displayName,places.formattedAddress,places.location,places.types,places.rating,places.userRatingCount,places.priceLevel,places.currentOpeningHours.openNow,places.businessStatus',
}),
body: (params) => {
const body: {
locationRestriction: {
circle: { center: { latitude: number; longitude: number }; radius: number }
}
includedTypes?: string[]
maxResultCount?: number
rankPreference?: string
languageCode?: string
regionCode?: string
} = {
locationRestriction: {
circle: {
center: { latitude: params.lat, longitude: params.lng },
radius: params.radius,
},
},
}
if (params.includedTypes && params.includedTypes.length > 0) {
body.includedTypes = params.includedTypes
}
if (params.maxResultCount) {
body.maxResultCount = params.maxResultCount
}
if (params.rankPreference) {
body.rankPreference = params.rankPreference
}
if (params.languageCode) {
body.languageCode = params.languageCode.trim()
}
if (params.regionCode) {
body.regionCode = params.regionCode.trim()
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok || data.error) {
throw new Error(`Places Nearby Search failed: ${data.error?.message || response.statusText}`)
}
const places = (data.places || []).map(
(place: {
id: string
displayName?: { text?: string }
formattedAddress?: string
location?: { latitude: number; longitude: number }
types?: string[]
rating?: number
userRatingCount?: number
priceLevel?: string
currentOpeningHours?: { openNow?: boolean }
businessStatus?: string
}) => ({
placeId: place.id,
name: place.displayName?.text || '',
formattedAddress: place.formattedAddress ?? null,
lat: place.location?.latitude ?? null,
lng: place.location?.longitude ?? null,
types: place.types ?? [],
rating: place.rating ?? null,
userRatingsTotal: place.userRatingCount ?? null,
priceLevel: place.priceLevel ?? null,
openNow: place.currentOpeningHours?.openNow ?? null,
businessStatus: place.businessStatus ?? null,
})
)
return {
success: true,
output: {
places,
},
}
},
outputs: {
places: {
type: 'array',
description: 'List of places found near the given location',
items: {
type: 'object',
properties: {
placeId: { type: 'string', description: 'Google Place resource ID' },
name: { type: 'string', description: 'Place name' },
formattedAddress: { type: 'string', description: 'Formatted address', optional: true },
lat: { type: 'number', description: 'Latitude', optional: true },
lng: { type: 'number', description: 'Longitude', optional: true },
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: 'string',
description: 'Price level (e.g., PRICE_LEVEL_MODERATE)',
optional: true,
},
openNow: { type: 'boolean', description: 'Whether currently open', optional: true },
businessStatus: { type: 'string', description: 'Business status', optional: true },
},
},
},
},
}
+194
View File
@@ -0,0 +1,194 @@
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)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Token from a previous search response to fetch the next page of results. Wait a couple seconds after receiving the token before using it, or the API returns INVALID_REQUEST',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.032,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
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())
}
if (params.pageToken) {
url.searchParams.set('pagetoken', params.pageToken.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,
},
},
}
+249
View File
@@ -0,0 +1,249 @@
import type { GoogleMapsPollenParams, GoogleMapsPollenResponse } from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
interface RawIndexInfo {
code?: string
displayName?: string
value?: number
category?: string
indexDescription?: string
color?: { red?: number; green?: number; blue?: number }
}
const mapIndexInfo = (indexInfo: RawIndexInfo | undefined) =>
indexInfo
? {
code: indexInfo.code || '',
displayName: indexInfo.displayName || '',
value: indexInfo.value ?? 0,
category: indexInfo.category || '',
indexDescription: indexInfo.indexDescription || '',
color: {
red: indexInfo.color?.red ?? 0,
green: indexInfo.color?.green ?? 0,
blue: indexInfo.color?.blue ?? 0,
},
}
: null
export const googleMapsPollenTool: ToolConfig<GoogleMapsPollenParams, GoogleMapsPollenResponse> = {
id: 'google_maps_pollen',
name: 'Google Maps Pollen',
description: 'Get a daily pollen forecast (grass, tree, weed) for a location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Pollen API enabled',
},
lat: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude coordinate',
},
days: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of forecast days to return (1-5, defaults to 1)',
},
languageCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Language code for the response (e.g., "en", "es")',
},
plantsDescription: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include detailed plant descriptions (defaults to true)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://pollen.googleapis.com/v1/forecast:lookup')
url.searchParams.set('location.latitude', params.lat.toString())
url.searchParams.set('location.longitude', params.lng.toString())
const rawDays =
typeof params.days === 'number' && Number.isFinite(params.days)
? Math.trunc(params.days)
: 1
const days = Math.min(Math.max(rawDays, 1), 5)
url.searchParams.set('days', days.toString())
if (params.languageCode) {
url.searchParams.set('languageCode', params.languageCode.trim())
}
if (params.plantsDescription !== undefined) {
url.searchParams.set('plantsDescription', String(params.plantsDescription))
}
url.searchParams.set('key', params.apiKey.trim())
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok || data.error) {
throw new Error(`Pollen lookup failed: ${data.error?.message || response.statusText}`)
}
const dailyInfo = (data.dailyInfo || []).map(
(day: {
date?: { year?: number; month?: number; day?: number }
pollenTypeInfo?: Array<{
code?: string
displayName?: string
inSeason?: boolean
indexInfo?: RawIndexInfo
healthRecommendations?: string[]
}>
plantInfo?: Array<{
code?: string
displayName?: string
inSeason?: boolean
indexInfo?: RawIndexInfo
plantDescription?: {
type?: string
family?: string
season?: string
specialColors?: string
specialShapes?: string
crossReaction?: string
picture?: string
pictureCloseup?: string
}
}>
}) => ({
date: {
year: day.date?.year ?? 0,
month: day.date?.month ?? 0,
day: day.date?.day ?? 0,
},
pollenTypeInfo: (day.pollenTypeInfo || []).map((type) => ({
code: type.code || '',
displayName: type.displayName || '',
inSeason: type.inSeason ?? null,
indexInfo: mapIndexInfo(type.indexInfo),
healthRecommendations: type.healthRecommendations || [],
})),
plantInfo: (day.plantInfo || []).map((plant) => ({
code: plant.code || '',
displayName: plant.displayName || '',
inSeason: plant.inSeason ?? null,
indexInfo: mapIndexInfo(plant.indexInfo),
plantDescription: plant.plantDescription
? {
type: plant.plantDescription.type || '',
family: plant.plantDescription.family || '',
season: plant.plantDescription.season || '',
specialColors: plant.plantDescription.specialColors || '',
specialShapes: plant.plantDescription.specialShapes || '',
crossReaction: plant.plantDescription.crossReaction || '',
picture: plant.plantDescription.picture || '',
pictureCloseup: plant.plantDescription.pictureCloseup || '',
}
: null,
})),
})
)
return {
success: true,
output: {
regionCode: data.regionCode || '',
dailyInfo,
},
}
},
outputs: {
regionCode: {
type: 'string',
description: 'Region code (ISO 3166-1 alpha-2) for the location',
},
dailyInfo: {
type: 'array',
description: 'Daily pollen forecast entries',
items: {
type: 'object',
properties: {
date: {
type: 'object',
description: 'Calendar date of the forecast entry',
properties: {
year: { type: 'number' },
month: { type: 'number' },
day: { type: 'number' },
},
},
pollenTypeInfo: {
type: 'array',
description: 'Pollen type indices (grass, tree, weed)',
items: {
type: 'object',
properties: {
code: { type: 'string', description: 'Pollen type code (GRASS, TREE, WEED)' },
displayName: { type: 'string', description: 'Display name' },
inSeason: { type: 'boolean', description: 'Whether the pollen type is in season' },
indexInfo: { type: 'object', description: 'Universal Pollen Index (UPI) info' },
healthRecommendations: {
type: 'array',
description: 'Health recommendations',
items: { type: 'string' },
},
},
},
},
plantInfo: {
type: 'array',
description: 'Per-plant forecast with descriptions',
items: {
type: 'object',
properties: {
code: { type: 'string', description: 'Plant code (e.g., BIRCH, RAGWEED)' },
displayName: { type: 'string', description: 'Display name' },
inSeason: { type: 'boolean', description: 'Whether the plant is in season' },
indexInfo: { type: 'object', description: 'Universal Pollen Index (UPI) info' },
plantDescription: {
type: 'object',
description: 'Plant details (type, family, season, cross-reactions)',
},
},
},
},
},
},
},
},
}
@@ -0,0 +1,131 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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',
},
},
},
}
+127
View File
@@ -0,0 +1,127 @@
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',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.01,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
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)',
},
},
}
+158
View File
@@ -0,0 +1,158 @@
import type { GoogleMapsSolarParams, GoogleMapsSolarResponse } from '@/tools/google_maps/types'
import type { ToolConfig } from '@/tools/types'
export const googleMapsSolarTool: ToolConfig<GoogleMapsSolarParams, GoogleMapsSolarResponse> = {
id: 'google_maps_solar',
name: 'Google Maps Solar',
description: 'Get solar potential and panel insights for the building nearest a location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Google Maps API key with Solar API enabled',
},
lat: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Latitude coordinate',
},
lng: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Longitude coordinate',
},
requiredQuality: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Minimum imagery quality to accept (HIGH, MEDIUM, or BASE)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://solar.googleapis.com/v1/buildingInsights:findClosest')
url.searchParams.set('location.latitude', params.lat.toString())
url.searchParams.set('location.longitude', params.lng.toString())
if (params.requiredQuality) {
url.searchParams.set('requiredQuality', params.requiredQuality)
}
url.searchParams.set('key', params.apiKey.trim())
return url.toString()
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok || data.error) {
throw new Error(`Solar lookup failed: ${data.error?.message || response.statusText}`)
}
const potential = data.solarPotential
const solarPotential = potential
? {
maxArrayPanelsCount: potential.maxArrayPanelsCount ?? 0,
maxArrayAreaMeters2: potential.maxArrayAreaMeters2 ?? 0,
maxSunshineHoursPerYear: potential.maxSunshineHoursPerYear ?? 0,
carbonOffsetFactorKgPerMwh: potential.carbonOffsetFactorKgPerMwh ?? 0,
panelCapacityWatts: potential.panelCapacityWatts ?? 0,
panelHeightMeters: potential.panelHeightMeters ?? 0,
panelWidthMeters: potential.panelWidthMeters ?? 0,
panelLifetimeYears: potential.panelLifetimeYears ?? 0,
solarPanelConfigs: (potential.solarPanelConfigs || []).map(
(config: { panelsCount?: number; yearlyEnergyDcKwh?: number }) => ({
panelsCount: config.panelsCount ?? 0,
yearlyEnergyDcKwh: config.yearlyEnergyDcKwh ?? 0,
})
),
}
: null
return {
success: true,
output: {
name: data.name || '',
center: {
lat: data.center?.latitude ?? 0,
lng: data.center?.longitude ?? 0,
},
imageryDate: data.imageryDate
? {
year: data.imageryDate.year ?? 0,
month: data.imageryDate.month ?? 0,
day: data.imageryDate.day ?? 0,
}
: null,
imageryQuality: data.imageryQuality || '',
regionCode: data.regionCode || '',
postalCode: data.postalCode || '',
administrativeArea: data.administrativeArea || '',
solarPotential,
},
}
},
outputs: {
name: {
type: 'string',
description: 'Resource name of the building (e.g., "buildings/ChIJ...")',
},
center: {
type: 'object',
description: 'Center coordinate of the building',
properties: {
lat: { type: 'number', description: 'Latitude' },
lng: { type: 'number', description: 'Longitude' },
},
},
imageryDate: {
type: 'object',
description: 'Date the underlying imagery was captured',
},
imageryQuality: {
type: 'string',
description: 'Quality of the imagery used (HIGH, MEDIUM, BASE)',
},
regionCode: {
type: 'string',
description: 'Region code (ISO 3166-1 alpha-2) for the building',
},
postalCode: {
type: 'string',
description: 'Postal code of the building',
},
administrativeArea: {
type: 'string',
description: 'Administrative area (e.g., state or province)',
},
solarPotential: {
type: 'object',
description:
'Solar potential: max panel count/area, sunshine hours, carbon offset, panel specs, and configs',
},
},
}
+151
View File
@@ -0,0 +1,151 @@
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. Requires either path coordinates or placeIds.',
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 (required if placeIds not provided)',
},
placeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of Place IDs for road segments (required if path not provided)',
},
units: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Units for the returned speed limits: KPH (default) or MPH',
},
},
request: {
url: (params) => {
const hasPath = params.path && params.path.trim().length > 0
const hasPlaceIds = params.placeIds && params.placeIds.length > 0
if (!hasPath && !hasPlaceIds) {
throw new Error(
'Speed Limits requires either a path (coordinates) or placeIds. Please provide at least one.'
)
}
const url = new URL('https://roads.googleapis.com/v1/speedLimits')
url.searchParams.set('key', params.apiKey.trim())
if (hasPath) {
url.searchParams.set('path', params.path!.trim())
}
if (hasPlaceIds) {
for (const placeId of params.placeIds!) {
url.searchParams.append('placeId', placeId.trim())
}
}
if (params.units) {
url.searchParams.set('units', params.units)
}
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' },
},
},
},
},
}
+134
View File
@@ -0,0 +1,134 @@
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)',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
const url = new URL('https://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)',
},
},
}
+642
View File
@@ -0,0 +1,642 @@
import type { ToolResponse } from '@/tools/types'
/**
* Common location type
*/
interface LatLng {
lat: number
lng: number
}
/**
* Address component from geocoding
*/
interface AddressComponent {
longName: string
shortName: string
types: string[]
}
/**
* Snapped point from Roads API
*/
interface SnappedPoint {
location: LatLng
originalIndex?: number
placeId: string
}
/**
* Speed limit info from Roads API
*/
interface SpeedLimit {
placeId: string
speedLimit: number
units: 'KPH' | 'MPH'
}
/**
* Cell tower info for geolocation
*/
interface CellTower {
cellId: number
locationAreaCode: number
mobileCountryCode: number
mobileNetworkCode: number
age?: number
signalStrength?: number
timingAdvance?: number
}
/**
* WiFi access point info for geolocation
*/
interface WifiAccessPoint {
macAddress: string
signalStrength?: number
age?: number
channel?: number
signalToNoiseRatio?: number
}
/**
* Air quality index info
*/
interface AirQualityIndex {
code: string
displayName: string
aqi: number
aqiDisplay: string
color: {
red: number
green: number
blue: number
}
category: string
dominantPollutant: string
}
/**
* Pollutant concentration info
*/
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
// ============================================================================
interface DirectionsStep {
instruction: string
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
startLocation: LatLng
endLocation: LatLng
travelMode: string
maneuver: string | null
}
interface DirectionsLeg {
startAddress: string
endAddress: string
startLocation: LatLng
endLocation: LatLng
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
steps: DirectionsStep[]
}
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
// ============================================================================
interface DistanceMatrixElement {
distanceText: string
distanceMeters: number
durationText: string
durationSeconds: number
durationInTrafficText: string | null
durationInTrafficSeconds: number | null
status: string
}
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
// ============================================================================
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
pageToken?: string
}
export interface GoogleMapsPlacesSearchResponse extends ToolResponse {
output: {
places: PlaceResult[]
nextPageToken: string | null
}
}
// ============================================================================
// Places Nearby Search
// ============================================================================
interface NearbyPlaceResult {
placeId: string
name: string
formattedAddress: string | null
lat: number | null
lng: number | null
types: string[]
rating: number | null
userRatingsTotal: number | null
priceLevel: string | null
openNow: boolean | null
businessStatus: string | null
}
export interface GoogleMapsPlacesNearbyParams {
apiKey: string
lat: number
lng: number
radius: number
includedTypes?: string[]
maxResultCount?: number
rankPreference?: 'POPULARITY' | 'DISTANCE'
languageCode?: string
regionCode?: string
}
export interface GoogleMapsPlacesNearbyResponse extends ToolResponse {
output: {
places: NearbyPlaceResult[]
}
}
// ============================================================================
// Place Details
// ============================================================================
interface PlaceReview {
authorName: string
authorUrl: string | null
profilePhotoUrl: string | null
rating: number
text: string
time: number
relativeTimeDescription: string
}
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 | null
}
}
// ============================================================================
// 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[]
units?: 'KPH' | 'MPH'
}
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
}
}
// ============================================================================
// Pollen
// ============================================================================
/**
* Calendar date returned by the Pollen and Solar APIs
*/
interface GoogleMapsDate {
year: number
month: number
day: number
}
/**
* Universal pollen index info shared by pollen types and plants
*/
interface PollenIndexInfo {
code: string
displayName: string
value: number
category: string
indexDescription: string
color: {
red: number
green: number
blue: number
}
}
/**
* Pollen type forecast (grass, tree, weed)
*/
interface PollenTypeInfo {
code: string
displayName: string
inSeason: boolean | null
indexInfo: PollenIndexInfo | null
healthRecommendations: string[]
}
/**
* Individual plant forecast with optional description
*/
interface PlantInfo {
code: string
displayName: string
inSeason: boolean | null
indexInfo: PollenIndexInfo | null
plantDescription: {
type: string
family: string
season: string
specialColors: string
specialShapes: string
crossReaction: string
picture: string
pictureCloseup: string
} | null
}
interface PollenDailyInfo {
date: GoogleMapsDate
pollenTypeInfo: PollenTypeInfo[]
plantInfo: PlantInfo[]
}
export interface GoogleMapsPollenParams {
apiKey: string
lat: number
lng: number
days?: number
languageCode?: string
plantsDescription?: boolean
}
export interface GoogleMapsPollenResponse extends ToolResponse {
output: {
regionCode: string
dailyInfo: PollenDailyInfo[]
}
}
// ============================================================================
// Solar
// ============================================================================
interface SolarPanelConfig {
panelsCount: number
yearlyEnergyDcKwh: number
}
interface SolarPotential {
maxArrayPanelsCount: number
maxArrayAreaMeters2: number
maxSunshineHoursPerYear: number
carbonOffsetFactorKgPerMwh: number
panelCapacityWatts: number
panelHeightMeters: number
panelWidthMeters: number
panelLifetimeYears: number
solarPanelConfigs: SolarPanelConfig[]
}
export interface GoogleMapsSolarParams {
apiKey: string
lat: number
lng: number
requiredQuality?: 'HIGH' | 'MEDIUM' | 'BASE'
}
export interface GoogleMapsSolarResponse extends ToolResponse {
output: {
name: string
center: LatLng
imageryDate: GoogleMapsDate | null
imageryQuality: string
regionCode: string
postalCode: string
administrativeArea: string
solarPotential: SolarPotential | null
}
}
@@ -0,0 +1,208 @@
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',
},
},
hosting: {
envKeyPrefix: 'GOOGLE_CLOUD_API_KEY',
apiKeyParam: 'apiKey',
byokProviderId: 'google_cloud',
pricing: {
type: 'per_request',
cost: 0.005,
},
rateLimit: {
mode: 'per_request',
requestsPerMinute: 60,
},
},
request: {
url: (params) => {
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',
},
},
}