Compare commits

...

1 Commits

Author SHA1 Message Date
Waleed Latif
83f3be846c feat(google-analytics): add Google Analytics GA4 integration 2026-03-15 03:42:32 -07:00
27 changed files with 1264 additions and 24 deletions

View File

@@ -3600,6 +3600,25 @@ export const ResendIcon = (props: SVGProps<SVGSVGElement>) => (
</svg>
)
export const GoogleAnalyticsIcon = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
<g transform='translate(-174.95 -339.18)scale(3.54856)'>
<path
d='M149.3 112.8V221c0 12.1 8.4 18.9 17.2 18.9 8.2 0 17.2-5.7 17.2-18.9V113.6c0-11.1-8.2-18-17.2-18s-17.2 7.6-17.2 17.2'
fill='#f8ab00'
/>
<path
d='M104.2 167.7V221c0 12.1 8.4 18.9 17.2 18.9 8.2 0 17.2-5.7 17.2-18.9v-52.5c0-11.1-8.2-18-17.2-18s-17.2 7.7-17.2 17.2'
fill='#e37300'
/>
<path
d='M93.6 222.7c0 9.5-7.7 17.2-17.2 17.2s-17.2-7.7-17.2-17.2 7.7-17.2 17.2-17.2 17.2 7.6 17.2 17.2'
fill='#e37300'
/>
</g>
</svg>
)
export const GoogleAdsIcon = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>
<g transform='matrix(.257748 0 0 .257745 -.361416 2.515516)'>

View File

@@ -47,11 +47,12 @@ import {
FirecrawlIcon,
FirefliesIcon,
GammaIcon,
GithubIcon,
GitLabIcon,
GithubIcon,
GmailIcon,
GongIcon,
GoogleAdsIcon,
GoogleAnalyticsIcon,
GoogleBigQueryIcon,
GoogleBooksIcon,
GoogleCalendarIcon,
@@ -91,9 +92,9 @@ import {
LinkupIcon,
LoopsIcon,
LumaIcon,
MailServerIcon,
MailchimpIcon,
MailgunIcon,
MailServerIcon,
Mem0Icon,
MicrosoftDataverseIcon,
MicrosoftExcelIcon,
@@ -128,6 +129,8 @@ import {
ResendIcon,
RevenueCatIcon,
S3Icon,
SQSIcon,
STTIcon,
SalesforceIcon,
SearchIcon,
SendgridIcon,
@@ -139,19 +142,17 @@ import {
SimilarwebIcon,
SlackIcon,
SmtpIcon,
SQSIcon,
SshIcon,
STTIcon,
StagehandIcon,
StripeIcon,
SupabaseIcon,
TTSIcon,
TavilyIcon,
TelegramIcon,
TextractIcon,
TinybirdIcon,
TranslateIcon,
TrelloIcon,
TTSIcon,
TwilioIcon,
TypeformIcon,
UpstashIcon,
@@ -162,11 +163,11 @@ import {
WhatsAppIcon,
WikipediaIcon,
WordpressIcon,
xIcon,
YouTubeIcon,
ZendeskIcon,
ZepIcon,
ZoomIcon,
xIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
@@ -218,6 +219,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
gmail_v2: GmailIcon,
gong: GongIcon,
google_ads: GoogleAdsIcon,
google_analytics: GoogleAnalyticsIcon,
google_bigquery: GoogleBigQueryIcon,
google_books: GoogleBooksIcon,
google_calendar_v2: GoogleCalendarIcon,

View File

@@ -53,6 +53,9 @@ Extract structured content from web pages with comprehensive metadata support. C
| `url` | string | Yes | The URL to scrape content from \(e.g., "https://example.com/page"\) |
| `scrapeOptions` | json | No | Options for content scraping |
| `apiKey` | string | Yes | Firecrawl API key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -86,6 +89,9 @@ Search for information on the web using Firecrawl
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | The search query to use |
| `apiKey` | string | Yes | Firecrawl API key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -123,6 +129,9 @@ Crawl entire websites and extract structured content from all accessible pages
| `includePaths` | json | No | URL paths to include in crawling \(e.g., \["/docs/*", "/api/*"\]\). Only these paths will be crawled |
| `onlyMainContent` | boolean | No | Extract only main content from pages |
| `apiKey` | string | Yes | Firecrawl API Key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -142,7 +151,6 @@ Crawl entire websites and extract structured content from all accessible pages
| ↳ `statusCode` | number | HTTP status code |
| ↳ `ogLocaleAlternate` | array | Alternate locale versions |
| `total` | number | Total number of pages found during crawl |
| `creditsUsed` | number | Number of credits consumed by the crawl operation |
### `firecrawl_map`
@@ -161,6 +169,9 @@ Get a complete list of URLs from any website quickly and reliably. Useful for di
| `timeout` | number | No | Request timeout in milliseconds |
| `location` | json | No | Geographic context for proxying \(country, languages\) |
| `apiKey` | string | Yes | Firecrawl API key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -187,6 +198,9 @@ Extract structured data from entire webpages using natural language prompts and
| `ignoreInvalidURLs` | boolean | No | Skip invalid URLs in the array \(default: true\) |
| `scrapeOptions` | json | No | Advanced scraping configuration options |
| `apiKey` | string | Yes | Firecrawl API key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -217,7 +231,6 @@ Autonomous web data extraction agent. Searches and gathers information based on
| `success` | boolean | Whether the agent operation was successful |
| `status` | string | Current status of the agent job \(processing, completed, failed\) |
| `data` | object | Extracted data from the agent |
| `creditsUsed` | number | Number of credits consumed by this agent task |
| `expiresAt` | string | Timestamp when the results expire \(24 hours\) |
| `sources` | object | Array of source URLs used by the agent |

View File

@@ -0,0 +1,129 @@
---
title: Google Analytics
description: Query GA4 analytics data and reports
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_analytics"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Google Analytics](https://analytics.google.com) is Google's web and app analytics platform. Through Sim, your agents can query GA4 property data to automate reporting, monitoring, and analysis workflows.
**The following Google Analytics Data API operations are included in this integration:**
- **Run Report:** Generate customized reports with dimensions, metrics, date ranges, filters, and sorting. Supports pagination for large datasets.
- **Run Realtime Report:** Get live data from the last 30 minutes, including active users, page views, and conversions in real time.
- **Get Metadata:** Discover all available dimensions and metrics for a GA4 property, including their descriptions and categories.
With these operations, your Sim agents can automate daily/weekly analytics reporting, monitor real-time traffic and conversions, build dashboards from GA4 data, detect anomalies in key metrics, and enrich workflows with analytics context—all without manual work in the Google Analytics UI.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Google Analytics GA4 into your workflow. Run custom reports, get realtime data, and discover available dimensions and metrics.
## Tools
### `google_analytics_run_report`
Run a customized report on Google Analytics GA4 property data
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\) |
| `dimensions` | string | Yes | Comma-separated dimension names \(e.g., date,country,deviceCategory\). See GA4 dimensions reference. |
| `metrics` | string | Yes | Comma-separated metric names \(e.g., activeUsers,sessions,screenPageViews\). See GA4 metrics reference. |
| `startDate` | string | Yes | Start date in YYYY-MM-DD format, or relative dates like "7daysAgo", "30daysAgo", "yesterday" |
| `endDate` | string | Yes | End date in YYYY-MM-DD format, or "today", "yesterday" |
| `dimensionFilter` | string | No | Dimension filter as JSON \(e.g., \{"filter":\{"fieldName":"country","stringFilter":\{"value":"US"\}\}\}\) |
| `metricFilter` | string | No | Metric filter as JSON \(e.g., \{"filter":\{"fieldName":"activeUsers","numericFilter":\{"operation":"GREATER_THAN","value":\{"int64Value":"100"\}\}\}\}\) |
| `orderBys` | string | No | Order by specification as JSON array \(e.g., \[\{"metric":\{"metricName":"activeUsers"\},"desc":true\}\]\) |
| `limit` | number | No | Maximum number of rows to return \(default: 10000, max: 250000\) |
| `offset` | number | No | Starting row offset for pagination \(default: 0\) |
| `keepEmptyRows` | boolean | No | Whether to include rows with all zero metric values |
| `currencyCode` | string | No | Currency code for revenue metrics \(e.g., USD, EUR\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dimensionHeaders` | array | Dimension column headers |
| ↳ `name` | string | Dimension name |
| `metricHeaders` | array | Metric column headers |
| ↳ `name` | string | Metric name |
| ↳ `type` | string | Metric data type |
| `rows` | array | Report data rows |
| ↳ `dimensionValues` | json | Array of dimension values for this row |
| ↳ `metricValues` | json | Array of metric values for this row |
| `rowCount` | number | Total number of rows in the result |
| `metadata` | json | Report metadata including currency code and time zone |
| ↳ `currencyCode` | string | Currency code used in the report |
| ↳ `timeZone` | string | Time zone used in the report |
### `google_analytics_run_realtime_report`
Run a realtime report on Google Analytics GA4 property data from the last 30 minutes
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\) |
| `dimensions` | string | No | Comma-separated dimension names for realtime data \(e.g., unifiedScreenName,country,deviceCategory\) |
| `metrics` | string | Yes | Comma-separated metric names \(e.g., activeUsers,screenPageViews,conversions\) |
| `dimensionFilter` | string | No | Dimension filter as JSON |
| `metricFilter` | string | No | Metric filter as JSON |
| `limit` | number | No | Maximum number of rows to return \(default: 10000, max: 250000\) |
| `startMinutesAgo` | number | No | Start of the time window in minutes ago \(default: 29, max: 29 for standard, 59 for 360\) |
| `endMinutesAgo` | number | No | End of the time window in minutes ago \(default: 0, meaning now\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dimensionHeaders` | array | Dimension column headers |
| ↳ `name` | string | Dimension name |
| `metricHeaders` | array | Metric column headers |
| ↳ `name` | string | Metric name |
| ↳ `type` | string | Metric data type |
| `rows` | array | Realtime report data rows |
| ↳ `dimensionValues` | json | Array of dimension values for this row |
| ↳ `metricValues` | json | Array of metric values for this row |
| `rowCount` | number | Total number of rows in the result |
### `google_analytics_get_metadata`
Get available dimensions, metrics, and their descriptions for a Google Analytics GA4 property
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `propertyId` | string | Yes | The GA4 property ID \(e.g., 123456789\). Use 0 to get universal metadata available across all properties. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dimensions` | array | Available dimensions for the property |
| ↳ `apiName` | string | API name to use in report requests |
| ↳ `uiName` | string | Human-readable display name |
| ↳ `description` | string | Description of the dimension |
| ↳ `category` | string | Category grouping |
| `metrics` | array | Available metrics for the property |
| ↳ `apiName` | string | API name to use in report requests |
| ↳ `uiName` | string | Human-readable display name |
| ↳ `description` | string | Description of the metric |
| ↳ `category` | string | Category grouping |
| ↳ `type` | string | Data type of the metric |

View File

@@ -46,6 +46,8 @@ Search for books using the Google Books API
| `startIndex` | number | No | Index of the first result to return \(for pagination\) |
| `maxResults` | number | No | Maximum number of results to return \(1-40\) |
| `langRestrict` | string | No | Restrict results to a specific language \(ISO 639-1 code\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -82,6 +84,8 @@ Get detailed information about a specific book volume
| `apiKey` | string | Yes | Google Books API key |
| `volumeId` | string | Yes | The ID of the volume to retrieve |
| `projection` | string | No | Projection level \(full, lite\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -50,6 +50,8 @@ Get current air quality data for a location
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `languageCode` | string | No | Language code for the response \(e.g., "en", "es"\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -91,6 +93,8 @@ Get directions and route information between two locations
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -135,6 +139,8 @@ Calculate travel distance and time between multiple origins and destinations
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -163,6 +169,8 @@ Get elevation data for a location
| `apiKey` | string | Yes | Google Maps API key |
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -185,6 +193,8 @@ Convert an address into geographic coordinates (latitude and longitude)
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -217,6 +227,8 @@ Geolocate a device using WiFi access points, cell towers, or IP address
| `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. |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -238,6 +250,8 @@ Get detailed information about a specific place
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -290,6 +304,8 @@ Search for places using a text query
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -322,6 +338,8 @@ Convert geographic coordinates (latitude and longitude) into a human-readable ad
| `lat` | number | Yes | Latitude coordinate |
| `lng` | number | Yes | Longitude coordinate |
| `language` | string | No | Language code for results \(e.g., en, es, fr\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -346,6 +364,8 @@ Snap GPS coordinates to the nearest road segment
| `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 |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -399,6 +419,8 @@ Get timezone information for a location
| `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\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -424,6 +446,8 @@ Validate and standardize a postal address
| `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 |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -55,6 +55,8 @@ Analyze a webpage for performance, accessibility, SEO, and best practices using
| `category` | string | No | Lighthouse categories to analyze \(comma-separated\): performance, accessibility, best-practices, seo |
| `strategy` | string | No | Analysis strategy: desktop or mobile |
| `locale` | string | No | Locale for results \(e.g., en, fr, de\) |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -43,6 +43,9 @@ Translate text between languages using the Google Cloud Translation API. Support
| `target` | string | Yes | Target language code \(e.g., "es", "fr", "de", "ja"\) |
| `source` | string | No | Source language code. If omitted, the API will auto-detect the source language. |
| `format` | string | No | Format of the text: "text" for plain text, "html" for HTML content |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -61,6 +64,9 @@ Detect the language of text using the Google Cloud Translation API.
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Google Cloud API key with Cloud Translation API enabled |
| `text` | string | Yes | The text to detect the language of |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -138,6 +138,26 @@ Get the full transcript of a recording
| ↳ `end` | number | End timestamp in ms |
| ↳ `text` | string | Transcript text |
### `grain_list_views`
List available Grain views for webhook subscriptions
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grain API key \(Personal Access Token\) |
| `typeFilter` | string | No | Optional view type filter: recordings, highlights, or stories |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `views` | array | Array of Grain views |
| ↳ `id` | string | View UUID |
| ↳ `name` | string | View name |
| ↳ `type` | string | View type: recordings, highlights, or stories |
### `grain_list_teams`
List all teams in the workspace
@@ -185,15 +205,9 @@ Create a webhook to receive recording events
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grain API key \(Personal Access Token\) |
| `hookUrl` | string | Yes | Webhook endpoint URL \(e.g., "https://example.com/webhooks/grain"\) |
| `hookType` | string | Yes | Type of webhook: "recording_added" or "upload_status" |
| `filterBeforeDatetime` | string | No | Filter: recordings before this ISO8601 date \(e.g., "2024-01-15T00:00:00Z"\) |
| `filterAfterDatetime` | string | No | Filter: recordings after this ISO8601 date \(e.g., "2024-01-01T00:00:00Z"\) |
| `filterParticipantScope` | string | No | Filter: "internal" or "external" |
| `filterTeamId` | string | No | Filter: specific team UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) |
| `filterMeetingTypeId` | string | No | Filter: specific meeting type UUID \(e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"\) |
| `includeHighlights` | boolean | No | Include highlights in webhook payload |
| `includeParticipants` | boolean | No | Include participants in webhook payload |
| `includeAiSummary` | boolean | No | Include AI summary in webhook payload |
| `viewId` | string | Yes | Grain view ID from GET /_/public-api/views |
| `actions` | array | No | Optional list of actions to subscribe to: added, updated, removed |
| `items` | string | No | No description |
#### Output
@@ -202,9 +216,8 @@ Create a webhook to receive recording events
| `id` | string | Hook UUID |
| `enabled` | boolean | Whether hook is active |
| `hook_url` | string | The webhook URL |
| `hook_type` | string | Type of hook: recording_added or upload_status |
| `filter` | object | Applied filters |
| `include` | object | Included fields |
| `view_id` | string | Grain view ID for the webhook |
| `actions` | array | Configured actions for the webhook |
| `inserted_at` | string | ISO8601 creation timestamp |
### `grain_list_hooks`
@@ -225,9 +238,8 @@ List all webhooks for the account
| ↳ `id` | string | Hook UUID |
| ↳ `enabled` | boolean | Whether hook is active |
| ↳ `hook_url` | string | Webhook URL |
| ↳ `hook_type` | string | Type: recording_added or upload_status |
| ↳ `filter` | object | Applied filters |
| ↳ `include` | object | Included fields |
| ↳ `view_id` | string | Grain view ID |
| ↳ `actions` | array | Configured actions |
| ↳ `inserted_at` | string | Creation timestamp |
### `grain_delete_hook`

View File

@@ -64,6 +64,7 @@ Extract and process web content into clean, LLM-friendly text using Jina AI Read
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | The extracted content from the URL, processed into clean, LLM-friendly text |
| `tokensUsed` | number | Number of Jina tokens consumed by this request |
### `jina_search`
@@ -97,5 +98,6 @@ Search the web and return top 5 results with LLM-friendly content. Each result i
| ↳ `content` | string | LLM-friendly extracted content |
| ↳ `usage` | object | Token usage information |
| ↳ `tokens` | number | Number of tokens consumed by this request |
| `tokensUsed` | number | Number of Jina tokens consumed by this request |

View File

@@ -51,6 +51,9 @@ Search the web for information using Linkup
| `includeDomains` | string | No | Comma-separated list of domain names to restrict search results to |
| `includeInlineCitations` | boolean | No | Add inline citations to answers \(only applies when outputType is "sourcedAnswer"\) |
| `includeSources` | boolean | No | Include sources in response |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -47,6 +47,7 @@
"gmail",
"gong",
"google_ads",
"google_analytics",
"google_bigquery",
"google_books",
"google_calendar",
@@ -167,4 +168,4 @@
"zep",
"zoom"
]
}
}

View File

@@ -49,6 +49,9 @@ Generate completions using Perplexity AI chat models
| `max_tokens` | number | No | Maximum number of tokens to generate \(e.g., 1024, 2048, 4096\) |
| `temperature` | number | No | Sampling temperature between 0 and 1 \(e.g., 0.0 for deterministic, 0.7 for creative\) |
| `apiKey` | string | Yes | Perplexity API key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
@@ -78,6 +81,8 @@ Get ranked search results from Perplexity
| `search_after_date` | string | No | Include only content published after this date \(format: MM/DD/YYYY\) |
| `search_before_date` | string | No | Include only content published before this date \(format: MM/DD/YYYY\) |
| `apiKey` | string | Yes | Perplexity API key |
| `pricing` | per_request | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -47,6 +47,9 @@ A powerful web search tool that provides access to Google search results through
| `hl` | string | No | Language code for search results \(e.g., "en", "es", "de", "fr"\) |
| `type` | string | No | Type of search to perform \(e.g., "search", "news", "images", "videos", "places", "shopping"\) |
| `apiKey` | string | Yes | Serper API Key |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output

View File

@@ -0,0 +1,301 @@
import { GoogleAnalyticsIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import { getScopesForService } from '@/lib/oauth/utils'
import type { GoogleAnalyticsResponse } from '@/tools/google_analytics/types'
export const GoogleAnalyticsBlock: BlockConfig<GoogleAnalyticsResponse> = {
type: 'google_analytics',
name: 'Google Analytics',
description: 'Query GA4 analytics data and reports',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Google Analytics GA4 into your workflow. Run custom reports, get realtime data, and discover available dimensions and metrics.',
docsLink: 'https://docs.sim.ai/tools/google_analytics',
category: 'tools',
bgColor: '#E0E0E0',
icon: GoogleAnalyticsIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Run Report', id: 'run_report' },
{ label: 'Run Realtime Report', id: 'run_realtime_report' },
{ label: 'Get Metadata', id: 'get_metadata' },
],
value: () => 'run_report',
},
{
id: 'credential',
title: 'Google Analytics Account',
type: 'oauth-input',
canonicalParamId: 'oauthCredential',
mode: 'basic',
required: true,
serviceId: 'google-analytics',
requiredScopes: getScopesForService('google-analytics'),
placeholder: 'Select Google Analytics account',
},
{
id: 'manualCredential',
title: 'Google Analytics Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
},
{
id: 'propertyId',
title: 'Property ID',
type: 'short-input',
placeholder: 'GA4 property ID (e.g., 123456789)',
required: true,
},
{
id: 'metrics',
title: 'Metrics',
type: 'short-input',
placeholder: 'e.g., activeUsers,sessions,screenPageViews',
condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] },
required: { field: 'operation', value: ['run_report', 'run_realtime_report'] },
wandConfig: {
enabled: true,
prompt: `Generate a comma-separated list of GA4 metric API names based on the user's description.
Common metrics: activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conversions, totalRevenue, newUsers, engagedSessions, engagementRate, eventsPerSession, eventCount, totalUsers.
Return ONLY the comma-separated metric names - no explanations, no extra text.`,
placeholder: 'Describe the metrics you want...',
},
},
{
id: 'dimensions',
title: 'Dimensions',
type: 'short-input',
placeholder: 'e.g., date,country,deviceCategory',
condition: { field: 'operation', value: 'run_report' },
required: { field: 'operation', value: 'run_report' },
wandConfig: {
enabled: true,
prompt: `Generate a comma-separated list of GA4 dimension API names based on the user's description.
Common dimensions: date, country, city, deviceCategory, browser, operatingSystem, sessionSource, sessionMedium, sessionCampaignName, pagePath, pageTitle, landingPage, language, newVsReturning.
Return ONLY the comma-separated dimension names - no explanations, no extra text.`,
placeholder: 'Describe the dimensions you want...',
},
},
{
id: 'realtimeDimensions',
title: 'Dimensions',
type: 'short-input',
placeholder: 'e.g., unifiedScreenName,country (optional)',
condition: { field: 'operation', value: 'run_realtime_report' },
wandConfig: {
enabled: true,
prompt: `Generate a comma-separated list of GA4 realtime dimension API names.
Common realtime dimensions: unifiedScreenName, country, city, deviceCategory, platform, streamName, audienceName.
Return ONLY the comma-separated dimension names - no explanations, no extra text.`,
placeholder: 'Describe the dimensions you want...',
},
},
{
id: 'startDate',
title: 'Start Date',
type: 'short-input',
placeholder: 'e.g., 7daysAgo, 30daysAgo, 2024-01-01',
condition: { field: 'operation', value: 'run_report' },
required: { field: 'operation', value: 'run_report' },
wandConfig: {
enabled: true,
prompt: 'Generate a GA4 date string. Supported formats: YYYY-MM-DD, or relative dates like "today", "yesterday", "NdaysAgo" (e.g., "7daysAgo", "30daysAgo"). Return ONLY the date string.',
generationType: 'timestamp',
},
},
{
id: 'endDate',
title: 'End Date',
type: 'short-input',
placeholder: 'e.g., today, yesterday, 2024-12-31',
condition: { field: 'operation', value: 'run_report' },
required: { field: 'operation', value: 'run_report' },
wandConfig: {
enabled: true,
prompt: 'Generate a GA4 date string. Supported formats: YYYY-MM-DD, or relative dates like "today", "yesterday", "NdaysAgo" (e.g., "7daysAgo", "30daysAgo"). Return ONLY the date string.',
generationType: 'timestamp',
},
},
{
id: 'dimensionFilter',
title: 'Dimension Filter',
type: 'long-input',
placeholder: 'JSON filter (e.g., {"filter":{"fieldName":"country","stringFilter":{"value":"US"}}})',
condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a GA4 dimension filter JSON object.
Format: {"filter":{"fieldName":"dimensionName","stringFilter":{"matchType":"EXACT","value":"filterValue"}}}
For multiple filters use: {"andGroup":{"expressions":[...]}} or {"orGroup":{"expressions":[...]}}
Match types: EXACT, BEGINS_WITH, ENDS_WITH, CONTAINS, FULL_REGEXP, PARTIAL_REGEXP.
Return ONLY valid JSON - no explanations.`,
generationType: 'json-object',
placeholder: 'Describe how to filter dimensions...',
},
},
{
id: 'metricFilter',
title: 'Metric Filter',
type: 'long-input',
placeholder: 'JSON filter for metrics',
condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a GA4 metric filter JSON object.
Format: {"filter":{"fieldName":"metricName","numericFilter":{"operation":"GREATER_THAN","value":{"int64Value":"100"}}}}
Operations: EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL.
Return ONLY valid JSON - no explanations.`,
generationType: 'json-object',
placeholder: 'Describe how to filter metrics...',
},
},
{
id: 'orderBys',
title: 'Order By',
type: 'long-input',
placeholder: 'JSON array (e.g., [{"metric":{"metricName":"activeUsers"},"desc":true}])',
condition: { field: 'operation', value: 'run_report' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a GA4 orderBys JSON array.
Format for metric sort: [{"metric":{"metricName":"metricName"},"desc":true}]
Format for dimension sort: [{"dimension":{"dimensionName":"dimensionName"},"desc":false}]
Return ONLY valid JSON array - no explanations.`,
generationType: 'json-object',
placeholder: 'Describe how to sort results...',
},
},
{
id: 'limit',
title: 'Row Limit',
type: 'short-input',
placeholder: 'Max rows to return (default: 10000)',
condition: { field: 'operation', value: ['run_report', 'run_realtime_report'] },
mode: 'advanced',
},
{
id: 'offset',
title: 'Row Offset',
type: 'short-input',
placeholder: 'Starting row for pagination (default: 0)',
condition: { field: 'operation', value: 'run_report' },
mode: 'advanced',
},
{
id: 'startMinutesAgo',
title: 'Start Minutes Ago',
type: 'short-input',
placeholder: 'Start of time window in minutes ago (default: 29)',
condition: { field: 'operation', value: 'run_realtime_report' },
mode: 'advanced',
},
{
id: 'endMinutesAgo',
title: 'End Minutes Ago',
type: 'short-input',
placeholder: 'End of time window in minutes ago (default: 0)',
condition: { field: 'operation', value: 'run_realtime_report' },
mode: 'advanced',
},
{
id: 'keepEmptyRows',
title: 'Keep Empty Rows',
type: 'dropdown',
options: [
{ label: 'No (default)', id: '' },
{ label: 'Yes', id: 'true' },
],
placeholder: 'Include rows with all zero metric values',
condition: { field: 'operation', value: 'run_report' },
mode: 'advanced',
},
{
id: 'currencyCode',
title: 'Currency Code',
type: 'short-input',
placeholder: 'e.g., USD, EUR',
condition: { field: 'operation', value: 'run_report' },
mode: 'advanced',
},
],
tools: {
access: [
'google_analytics_run_report',
'google_analytics_run_realtime_report',
'google_analytics_get_metadata',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'run_report':
return 'google_analytics_run_report'
case 'run_realtime_report':
return 'google_analytics_run_realtime_report'
case 'get_metadata':
return 'google_analytics_get_metadata'
default:
throw new Error(`Invalid Google Analytics operation: ${params.operation}`)
}
},
params: (params) => {
const { oauthCredential, realtimeDimensions, keepEmptyRows, ...rest } = params
return {
oauthCredential,
...rest,
dimensions: params.operation === 'run_realtime_report'
? realtimeDimensions
: rest.dimensions,
limit: rest.limit ? Number.parseInt(rest.limit as string, 10) : undefined,
offset: rest.offset ? Number.parseInt(rest.offset as string, 10) : undefined,
startMinutesAgo: rest.startMinutesAgo
? Number.parseInt(rest.startMinutesAgo as string, 10)
: undefined,
endMinutesAgo: rest.endMinutesAgo
? Number.parseInt(rest.endMinutesAgo as string, 10)
: undefined,
keepEmptyRows: keepEmptyRows === 'true' ? true : undefined,
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'Google Analytics access token' },
propertyId: { type: 'string', description: 'GA4 property ID' },
metrics: { type: 'string', description: 'Comma-separated metric names' },
dimensions: { type: 'string', description: 'Comma-separated dimension names' },
realtimeDimensions: { type: 'string', description: 'Comma-separated realtime dimension names' },
startDate: { type: 'string', description: 'Report start date' },
endDate: { type: 'string', description: 'Report end date' },
dimensionFilter: { type: 'string', description: 'Dimension filter JSON' },
metricFilter: { type: 'string', description: 'Metric filter JSON' },
orderBys: { type: 'string', description: 'Order by specification JSON' },
limit: { type: 'string', description: 'Maximum rows to return' },
offset: { type: 'string', description: 'Starting row offset' },
startMinutesAgo: { type: 'string', description: 'Realtime start minutes ago' },
endMinutesAgo: { type: 'string', description: 'Realtime end minutes ago' },
keepEmptyRows: { type: 'string', description: 'Include rows with all zero metric values' },
currencyCode: { type: 'string', description: 'Currency code for revenue metrics' },
},
outputs: {
dimensionHeaders: { type: 'json', description: 'Dimension column headers' },
metricHeaders: { type: 'json', description: 'Metric column headers' },
rows: { type: 'json', description: 'Report data rows' },
rowCount: { type: 'number', description: 'Total number of rows' },
metadata: { type: 'json', description: 'Report metadata (currency, timezone)' },
dimensions: { type: 'json', description: 'Available dimensions (from get_metadata)' },
metrics: { type: 'json', description: 'Available metrics (from get_metadata)' },
},
}

View File

@@ -53,6 +53,7 @@ import { GmailBlock, GmailV2Block } from '@/blocks/blocks/gmail'
import { GongBlock } from '@/blocks/blocks/gong'
import { GoogleSearchBlock } from '@/blocks/blocks/google'
import { GoogleAdsBlock } from '@/blocks/blocks/google_ads'
import { GoogleAnalyticsBlock } from '@/blocks/blocks/google_analytics'
import { GoogleBigQueryBlock } from '@/blocks/blocks/google_bigquery'
import { GoogleBooksBlock } from '@/blocks/blocks/google_books'
import { GoogleCalendarBlock, GoogleCalendarV2Block } from '@/blocks/blocks/google_calendar'
@@ -261,6 +262,7 @@ export const registry: Record<string, BlockConfig> = {
google_calendar: GoogleCalendarBlock,
google_calendar_v2: GoogleCalendarV2Block,
google_ads: GoogleAdsBlock,
google_analytics: GoogleAnalyticsBlock,
google_books: GoogleBooksBlock,
google_contacts: GoogleContactsBlock,
google_docs: GoogleDocsBlock,

View File

@@ -3600,6 +3600,25 @@ export const ResendIcon = (props: SVGProps<SVGSVGElement>) => (
</svg>
)
export const GoogleAnalyticsIcon = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
<g transform='translate(-174.95 -339.18)scale(3.54856)'>
<path
d='M149.3 112.8V221c0 12.1 8.4 18.9 17.2 18.9 8.2 0 17.2-5.7 17.2-18.9V113.6c0-11.1-8.2-18-17.2-18s-17.2 7.6-17.2 17.2'
fill='#f8ab00'
/>
<path
d='M104.2 167.7V221c0 12.1 8.4 18.9 17.2 18.9 8.2 0 17.2-5.7 17.2-18.9v-52.5c0-11.1-8.2-18-17.2-18s-17.2 7.7-17.2 17.2'
fill='#e37300'
/>
<path
d='M93.6 222.7c0 9.5-7.7 17.2-17.2 17.2s-17.2-7.7-17.2-17.2 7.7-17.2 17.2-17.2 17.2 7.6 17.2 17.2'
fill='#e37300'
/>
</g>
</svg>
)
export const GoogleAdsIcon = (props: SVGProps<SVGSVGElement>) => (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>
<g transform='matrix(.257748 0 0 .257745 -.361416 2.515516)'>

View File

@@ -970,6 +970,41 @@ export const auth = betterAuth({
}
},
},
{
providerId: 'google-analytics',
clientId: env.GOOGLE_CLIENT_ID as string,
clientSecret: env.GOOGLE_CLIENT_SECRET as string,
discoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration',
accessType: 'offline',
scopes: getCanonicalScopesForProvider('google-analytics'),
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-analytics`,
getUserInfo: async (tokens) => {
try {
const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
headers: { Authorization: `Bearer ${tokens.accessToken}` },
})
if (\!response.ok) {
logger.error('Failed to fetch Google user info', { status: response.status })
throw new Error(`Failed to fetch Google user info: ${response.statusText}`)
}
const profile = await response.json()
const now = new Date()
return {
id: `${profile.sub}-${crypto.randomUUID()}`,
name: profile.name || 'Google User',
email: profile.email,
image: profile.picture || undefined,
emailVerified: profile.email_verified || false,
createdAt: now,
updatedAt: now,
}
} catch (error) {
logger.error('Error in Google getUserInfo', { error })
throw error
}
},
},
{
providerId: 'google-ads',
clientId: env.GOOGLE_CLIENT_ID as string,

View File

@@ -8,6 +8,7 @@ import {
DropboxIcon,
GmailIcon,
GoogleAdsIcon,
GoogleAnalyticsIcon,
GoogleBigQueryIcon,
GoogleCalendarIcon,
GoogleContactsIcon,
@@ -147,6 +148,18 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'https://www.googleapis.com/auth/contacts',
],
},
'google-analytics': {
name: 'Google Analytics',
description: 'Query GA4 analytics data, run reports, and get realtime metrics.',
providerId: 'google-analytics',
icon: GoogleAnalyticsIcon,
baseProviderIcon: GoogleIcon,
scopes: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/analytics.readonly',
],
},
'google-ads': {
name: 'Google Ads',
description: 'Query campaigns, ad groups, and performance metrics in Google Ads.',

View File

@@ -9,6 +9,7 @@ export type OAuthProvider =
| 'google-calendar'
| 'google-contacts'
| 'google-ads'
| 'google-analytics'
| 'google-bigquery'
| 'google-tasks'
| 'google-vault'
@@ -57,6 +58,7 @@ export type OAuthService =
| 'google-calendar'
| 'google-contacts'
| 'google-ads'
| 'google-analytics'
| 'google-bigquery'
| 'google-tasks'
| 'google-vault'

View File

@@ -24,6 +24,7 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
'https://www.googleapis.com/auth/userinfo.profile': 'View basic profile info',
'https://www.googleapis.com/auth/forms.body': 'View and manage Google Forms',
'https://www.googleapis.com/auth/forms.responses.readonly': 'View responses to Google Forms',
'https://www.googleapis.com/auth/analytics.readonly': 'Read-only access to Google Analytics data',
'https://www.googleapis.com/auth/adwords': 'Manage Google Ads campaigns and reporting',
'https://www.googleapis.com/auth/bigquery': 'View and manage data in Google BigQuery',
'https://www.googleapis.com/auth/ediscovery': 'Access Google Vault for eDiscovery',

View File

@@ -0,0 +1,113 @@
import type { ToolConfig } from '@/tools/types'
import type {
GoogleAnalyticsGetMetadataParams,
GoogleAnalyticsGetMetadataResponse,
} from '@/tools/google_analytics/types'
export const getMetadataTool: ToolConfig<
GoogleAnalyticsGetMetadataParams,
GoogleAnalyticsGetMetadataResponse
> = {
id: 'google_analytics_get_metadata',
name: 'Get Google Analytics Metadata',
description:
'Get available dimensions, metrics, and their descriptions for a Google Analytics GA4 property',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-analytics',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Analytics API',
},
propertyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'The GA4 property ID (e.g., 123456789). Use 0 to get universal metadata available across all properties.',
},
},
request: {
url: (params) =>
`https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}/metadata`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to get Google Analytics metadata')
}
return {
success: true,
output: {
dimensions: (data.dimensions ?? []).map(
(d: { apiName: string; uiName: string; description: string; category: string }) => ({
apiName: d.apiName ?? '',
uiName: d.uiName ?? '',
description: d.description ?? '',
category: d.category ?? '',
})
),
metrics: (data.metrics ?? []).map(
(m: {
apiName: string
uiName: string
description: string
category: string
type: string
}) => ({
apiName: m.apiName ?? '',
uiName: m.uiName ?? '',
description: m.description ?? '',
category: m.category ?? '',
type: m.type ?? '',
})
),
},
}
},
outputs: {
dimensions: {
type: 'array',
description: 'Available dimensions for the property',
items: {
type: 'object',
properties: {
apiName: { type: 'string', description: 'API name to use in report requests' },
uiName: { type: 'string', description: 'Human-readable display name' },
description: { type: 'string', description: 'Description of the dimension' },
category: { type: 'string', description: 'Category grouping' },
},
},
},
metrics: {
type: 'array',
description: 'Available metrics for the property',
items: {
type: 'object',
properties: {
apiName: { type: 'string', description: 'API name to use in report requests' },
uiName: { type: 'string', description: 'Human-readable display name' },
description: { type: 'string', description: 'Description of the metric' },
category: { type: 'string', description: 'Category grouping' },
type: { type: 'string', description: 'Data type of the metric' },
},
},
},
},
}

View File

@@ -0,0 +1,9 @@
import { getMetadataTool } from '@/tools/google_analytics/get_metadata'
import { runRealtimeReportTool } from '@/tools/google_analytics/run_realtime_report'
import { runReportTool } from '@/tools/google_analytics/run_report'
export const googleAnalyticsRunReportTool = runReportTool
export const googleAnalyticsRunRealtimeReportTool = runRealtimeReportTool
export const googleAnalyticsGetMetadataTool = getMetadataTool
export * from './types'

View File

@@ -0,0 +1,185 @@
import type { ToolConfig } from '@/tools/types'
import type {
GoogleAnalyticsRunRealtimeReportParams,
GoogleAnalyticsRunRealtimeReportResponse,
} from '@/tools/google_analytics/types'
export const runRealtimeReportTool: ToolConfig<
GoogleAnalyticsRunRealtimeReportParams,
GoogleAnalyticsRunRealtimeReportResponse
> = {
id: 'google_analytics_run_realtime_report',
name: 'Run Google Analytics Realtime Report',
description: 'Run a realtime report on Google Analytics GA4 property data from the last 30 minutes',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-analytics',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Analytics API',
},
propertyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The GA4 property ID (e.g., 123456789)',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated dimension names for realtime data (e.g., unifiedScreenName,country,deviceCategory)',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated metric names (e.g., activeUsers,screenPageViews,conversions)',
},
dimensionFilter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Dimension filter as JSON',
},
metricFilter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Metric filter as JSON',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of rows to return (default: 10000, max: 250000)',
},
startMinutesAgo: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Start of the time window in minutes ago (default: 29, max: 29 for standard, 59 for 360)',
},
endMinutesAgo: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'End of the time window in minutes ago (default: 0, meaning now)',
},
},
request: {
url: (params) =>
`https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}:runRealtimeReport`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
metrics: params.metrics
.split(',')
.map((m: string) => ({ name: m.trim() })),
}
if (params.dimensions) {
body.dimensions = params.dimensions
.split(',')
.map((d: string) => ({ name: d.trim() }))
}
if (params.dimensionFilter) {
body.dimensionFilter = JSON.parse(params.dimensionFilter)
}
if (params.metricFilter) {
body.metricFilter = JSON.parse(params.metricFilter)
}
if (params.limit !== undefined) {
body.limit = params.limit
}
if (params.startMinutesAgo !== undefined || params.endMinutesAgo !== undefined) {
body.minuteRanges = [
{
startMinutesAgo: params.startMinutesAgo ?? 29,
endMinutesAgo: params.endMinutesAgo ?? 0,
},
]
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to run Google Analytics realtime report')
}
return {
success: true,
output: {
dimensionHeaders: data.dimensionHeaders ?? [],
metricHeaders: data.metricHeaders ?? [],
rows: data.rows ?? [],
rowCount: data.rowCount ?? null,
},
}
},
outputs: {
dimensionHeaders: {
type: 'array',
description: 'Dimension column headers',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Dimension name' },
},
},
},
metricHeaders: {
type: 'array',
description: 'Metric column headers',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Metric name' },
type: { type: 'string', description: 'Metric data type' },
},
},
},
rows: {
type: 'array',
description: 'Realtime report data rows',
items: {
type: 'object',
properties: {
dimensionValues: {
type: 'json',
description: 'Array of dimension values for this row',
},
metricValues: {
type: 'json',
description: 'Array of metric values for this row',
},
},
},
},
rowCount: {
type: 'number',
description: 'Total number of rows in the result',
optional: true,
},
},
}

View File

@@ -0,0 +1,230 @@
import type { ToolConfig } from '@/tools/types'
import type {
GoogleAnalyticsRunReportParams,
GoogleAnalyticsRunReportResponse,
} from '@/tools/google_analytics/types'
export const runReportTool: ToolConfig<
GoogleAnalyticsRunReportParams,
GoogleAnalyticsRunReportResponse
> = {
id: 'google_analytics_run_report',
name: 'Run Google Analytics Report',
description: 'Run a customized report on Google Analytics GA4 property data',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-analytics',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Analytics API',
},
propertyId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The GA4 property ID (e.g., 123456789)',
},
dimensions: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated dimension names (e.g., date,country,deviceCategory). See GA4 dimensions reference.',
},
metrics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated metric names (e.g., activeUsers,sessions,screenPageViews). See GA4 metrics reference.',
},
startDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date in YYYY-MM-DD format, or relative dates like "7daysAgo", "30daysAgo", "yesterday"',
},
endDate: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date in YYYY-MM-DD format, or "today", "yesterday"',
},
dimensionFilter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Dimension filter as JSON (e.g., {"filter":{"fieldName":"country","stringFilter":{"value":"US"}}})',
},
metricFilter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Metric filter as JSON (e.g., {"filter":{"fieldName":"activeUsers","numericFilter":{"operation":"GREATER_THAN","value":{"int64Value":"100"}}}})',
},
orderBys: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Order by specification as JSON array (e.g., [{"metric":{"metricName":"activeUsers"},"desc":true}])',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of rows to return (default: 10000, max: 250000)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Starting row offset for pagination (default: 0)',
},
keepEmptyRows: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include rows with all zero metric values',
},
currencyCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Currency code for revenue metrics (e.g., USD, EUR)',
},
},
request: {
url: (params) =>
`https://analyticsdata.googleapis.com/v1beta/properties/${params.propertyId.trim()}:runReport`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
dimensions: params.dimensions
.split(',')
.map((d: string) => ({ name: d.trim() })),
metrics: params.metrics
.split(',')
.map((m: string) => ({ name: m.trim() })),
dateRanges: [{ startDate: params.startDate, endDate: params.endDate }],
}
if (params.dimensionFilter) {
body.dimensionFilter = JSON.parse(params.dimensionFilter)
}
if (params.metricFilter) {
body.metricFilter = JSON.parse(params.metricFilter)
}
if (params.orderBys) {
body.orderBys = JSON.parse(params.orderBys)
}
if (params.limit !== undefined) {
body.limit = params.limit
}
if (params.offset !== undefined) {
body.offset = params.offset
}
if (params.keepEmptyRows !== undefined) {
body.keepEmptyRows = params.keepEmptyRows
}
if (params.currencyCode) {
body.currencyCode = params.currencyCode
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to run Google Analytics report')
}
return {
success: true,
output: {
dimensionHeaders: data.dimensionHeaders ?? [],
metricHeaders: data.metricHeaders ?? [],
rows: data.rows ?? [],
rowCount: data.rowCount ?? null,
metadata: data.metadata
? {
currencyCode: data.metadata.currencyCode ?? null,
timeZone: data.metadata.timeZone ?? null,
}
: null,
},
}
},
outputs: {
dimensionHeaders: {
type: 'array',
description: 'Dimension column headers',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Dimension name' },
},
},
},
metricHeaders: {
type: 'array',
description: 'Metric column headers',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Metric name' },
type: { type: 'string', description: 'Metric data type' },
},
},
},
rows: {
type: 'array',
description: 'Report data rows',
items: {
type: 'object',
properties: {
dimensionValues: {
type: 'json',
description: 'Array of dimension values for this row',
},
metricValues: {
type: 'json',
description: 'Array of metric values for this row',
},
},
},
},
rowCount: {
type: 'number',
description: 'Total number of rows in the result',
optional: true,
},
metadata: {
type: 'json',
description: 'Report metadata including currency code and time zone',
optional: true,
properties: {
currencyCode: { type: 'string', description: 'Currency code used in the report' },
timeZone: { type: 'string', description: 'Time zone used in the report' },
},
},
},
}

View File

@@ -0,0 +1,97 @@
import type { ToolResponse } from '@/tools/types'
export interface GoogleAnalyticsRunReportParams {
accessToken: string
propertyId: string
dimensions: string
metrics: string
startDate: string
endDate: string
dimensionFilter?: string
metricFilter?: string
orderBys?: string
limit?: number
offset?: number
keepEmptyRows?: boolean
currencyCode?: string
}
export interface GoogleAnalyticsRunRealtimeReportParams {
accessToken: string
propertyId: string
dimensions?: string
metrics: string
dimensionFilter?: string
metricFilter?: string
limit?: number
startMinutesAgo?: number
endMinutesAgo?: number
}
export interface GoogleAnalyticsGetMetadataParams {
accessToken: string
propertyId: string
}
export interface GoogleAnalyticsDimensionHeader {
name: string
}
export interface GoogleAnalyticsMetricHeader {
name: string
type: string
}
export interface GoogleAnalyticsRow {
dimensionValues: Array<{ value: string }>
metricValues: Array<{ value: string }>
}
export interface GoogleAnalyticsRunReportResponse extends ToolResponse {
output: {
dimensionHeaders: GoogleAnalyticsDimensionHeader[]
metricHeaders: GoogleAnalyticsMetricHeader[]
rows: GoogleAnalyticsRow[]
rowCount: number | null
metadata: {
currencyCode: string | null
timeZone: string | null
} | null
}
}
export interface GoogleAnalyticsRunRealtimeReportResponse extends ToolResponse {
output: {
dimensionHeaders: GoogleAnalyticsDimensionHeader[]
metricHeaders: GoogleAnalyticsMetricHeader[]
rows: GoogleAnalyticsRow[]
rowCount: number | null
}
}
export interface GoogleAnalyticsDimensionMetadata {
apiName: string
uiName: string
description: string
category: string
}
export interface GoogleAnalyticsMetricMetadata {
apiName: string
uiName: string
description: string
category: string
type: string
}
export interface GoogleAnalyticsGetMetadataResponse extends ToolResponse {
output: {
dimensions: GoogleAnalyticsDimensionMetadata[]
metrics: GoogleAnalyticsMetricMetadata[]
}
}
export type GoogleAnalyticsResponse =
| GoogleAnalyticsRunReportResponse
| GoogleAnalyticsRunRealtimeReportResponse
| GoogleAnalyticsGetMetadataResponse

View File

@@ -797,6 +797,11 @@ import {
googleDriveUpdateTool,
googleDriveUploadTool,
} from '@/tools/google_drive'
import {
googleAnalyticsGetMetadataTool,
googleAnalyticsRunRealtimeReportTool,
googleAnalyticsRunReportTool,
} from '@/tools/google_analytics'
import {
googleFormsBatchUpdateTool,
googleFormsCreateFormTool,
@@ -3305,6 +3310,9 @@ export const tools: Record<string, ToolConfig> = {
revenuecat_defer_google_subscription: revenuecatDeferGoogleSubscriptionTool,
revenuecat_refund_google_subscription: revenuecatRefundGoogleSubscriptionTool,
revenuecat_revoke_google_subscription: revenuecatRevokeGoogleSubscriptionTool,
google_analytics_run_report: googleAnalyticsRunReportTool,
google_analytics_run_realtime_report: googleAnalyticsRunRealtimeReportTool,
google_analytics_get_metadata: googleAnalyticsGetMetadataTool,
google_drive_copy: googleDriveCopyTool,
google_drive_create_folder: googleDriveCreateFolderTool,
google_drive_delete: googleDriveDeleteTool,