From 093cfac2d1fd79f2ef0b87b9cdd6bcd275c34dab Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Feb 2026 17:06:18 -0800 Subject: [PATCH 1/5] feat(google-ads): add google ads integration for campaign and ad performance queries --- apps/docs/components/icons.tsx | 21 ++ apps/docs/components/ui/icon-mapping.ts | 2 + .../docs/content/docs/en/tools/google_ads.mdx | 193 +++++++++++ apps/docs/content/docs/en/tools/meta.json | 1 + .../components/oauth-required-modal.tsx | 1 + apps/sim/blocks/blocks/google_ads.ts | 307 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 21 ++ apps/sim/lib/auth/auth.ts | 40 +++ apps/sim/lib/oauth/oauth.ts | 9 + apps/sim/lib/oauth/types.ts | 2 + apps/sim/tools/google_ads/ad_performance.ts | 203 ++++++++++++ .../tools/google_ads/campaign_performance.ts | 174 ++++++++++ apps/sim/tools/google_ads/index.ts | 15 + apps/sim/tools/google_ads/list_ad_groups.ts | 160 +++++++++ apps/sim/tools/google_ads/list_campaigns.ts | 162 +++++++++ apps/sim/tools/google_ads/list_customers.ts | 84 +++++ apps/sim/tools/google_ads/search.ts | 131 ++++++++ apps/sim/tools/google_ads/types.ts | 135 ++++++++ apps/sim/tools/registry.ts | 14 + 20 files changed, 1677 insertions(+) create mode 100644 apps/docs/content/docs/en/tools/google_ads.mdx create mode 100644 apps/sim/blocks/blocks/google_ads.ts create mode 100644 apps/sim/tools/google_ads/ad_performance.ts create mode 100644 apps/sim/tools/google_ads/campaign_performance.ts create mode 100644 apps/sim/tools/google_ads/index.ts create mode 100644 apps/sim/tools/google_ads/list_ad_groups.ts create mode 100644 apps/sim/tools/google_ads/list_campaigns.ts create mode 100644 apps/sim/tools/google_ads/list_customers.ts create mode 100644 apps/sim/tools/google_ads/search.ts create mode 100644 apps/sim/tools/google_ads/types.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 9e68974089..f8ffe44ffc 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3464,6 +3464,27 @@ export const ResendIcon = (props: SVGProps) => ( ) +export const GoogleAdsIcon = (props: SVGProps) => ( + + + + + + + + + +) + export const GoogleBigQueryIcon = (props: SVGProps) => ( = { gitlab: GitLabIcon, gmail_v2: GmailIcon, gong: GongIcon, + google_ads: GoogleAdsIcon, google_bigquery: GoogleBigQueryIcon, google_books: GoogleBooksIcon, google_calendar_v2: GoogleCalendarIcon, diff --git a/apps/docs/content/docs/en/tools/google_ads.mdx b/apps/docs/content/docs/en/tools/google_ads.mdx new file mode 100644 index 0000000000..8b7baa7d1a --- /dev/null +++ b/apps/docs/content/docs/en/tools/google_ads.mdx @@ -0,0 +1,193 @@ +--- +title: Google Ads +description: Query campaigns, ad groups, and performance metrics +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Google Ads](https://ads.google.com) is Google's online advertising platform that lets businesses create ads to reach customers across Google Search, YouTube, Gmail, and millions of partner websites. It supports campaign types including Search, Display, Video, Shopping, and Performance Max, with detailed targeting, bidding strategies, and performance analytics. + +In Sim, the Google Ads integration enables your agents to query campaign data, monitor ad group performance, and pull detailed metrics using the Google Ads Query Language (GAQL). This supports use cases such as automated performance reporting, budget monitoring, campaign health checks, and data-driven optimization workflows. By connecting Sim with Google Ads, your agents can retrieve real-time advertising data and act on insights without manual dashboard navigation. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Connect to Google Ads to list accessible accounts, list campaigns, view ad group details, get performance metrics, and run custom GAQL queries. + + + +## Tools + +### `google_ads_list_customers` + +List all Google Ads customer accounts accessible by the authenticated user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `developerToken` | string | Yes | Google Ads API developer token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `customerIds` | array | List of accessible customer IDs | +| `totalCount` | number | Total number of accessible customer accounts | + +### `google_ads_search` + +Run a custom Google Ads Query Language (GAQL) query + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customerId` | string | Yes | Google Ads customer ID \(numeric, no dashes\) | +| `developerToken` | string | Yes | Google Ads API developer token | +| `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | +| `query` | string | Yes | GAQL query to execute | +| `pageSize` | number | No | Maximum number of results per page \(max 10000\) | +| `pageToken` | string | No | Page token for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | json | Array of result objects from the GAQL query | +| `totalResultsCount` | number | Total number of matching results | +| `nextPageToken` | string | Token for the next page of results | + +### `google_ads_list_campaigns` + +List campaigns in a Google Ads account with optional status filtering + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customerId` | string | Yes | Google Ads customer ID \(numeric, no dashes\) | +| `developerToken` | string | Yes | Google Ads API developer token | +| `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | +| `status` | string | No | Filter by campaign status \(ENABLED, PAUSED, REMOVED\) | +| `limit` | number | No | Maximum number of campaigns to return | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `campaigns` | array | List of campaigns in the account | +| ↳ `id` | string | Campaign ID | +| ↳ `name` | string | Campaign name | +| ↳ `status` | string | Campaign status \(ENABLED, PAUSED, REMOVED\) | +| ↳ `channelType` | string | Advertising channel type \(SEARCH, DISPLAY, SHOPPING, VIDEO, PERFORMANCE_MAX\) | +| ↳ `startDate` | string | Campaign start date \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Campaign end date \(YYYY-MM-DD\) | +| ↳ `budgetAmountMicros` | string | Daily budget in micros \(divide by 1,000,000 for currency value\) | +| `totalCount` | number | Total number of campaigns returned | + +### `google_ads_campaign_performance` + +Get performance metrics for Google Ads campaigns over a date range + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customerId` | string | Yes | Google Ads customer ID \(numeric, no dashes\) | +| `developerToken` | string | Yes | Google Ads API developer token | +| `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | +| `campaignId` | string | No | Filter by specific campaign ID | +| `dateRange` | string | No | Predefined date range \(LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, TODAY, YESTERDAY\) | +| `startDate` | string | No | Custom start date in YYYY-MM-DD format | +| `endDate` | string | No | Custom end date in YYYY-MM-DD format | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `campaigns` | array | Campaign performance data broken down by date | +| ↳ `id` | string | Campaign ID | +| ↳ `name` | string | Campaign name | +| ↳ `status` | string | Campaign status | +| ↳ `impressions` | string | Number of impressions | +| ↳ `clicks` | string | Number of clicks | +| ↳ `costMicros` | string | Cost in micros \(divide by 1,000,000 for currency value\) | +| ↳ `ctr` | number | Click-through rate \(0.0 to 1.0\) | +| ↳ `conversions` | number | Number of conversions | +| ↳ `date` | string | Date for this row \(YYYY-MM-DD\) | +| `totalCount` | number | Total number of result rows | + +### `google_ads_list_ad_groups` + +List ad groups in a Google Ads campaign + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customerId` | string | Yes | Google Ads customer ID \(numeric, no dashes\) | +| `developerToken` | string | Yes | Google Ads API developer token | +| `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | +| `campaignId` | string | Yes | Campaign ID to list ad groups for | +| `status` | string | No | Filter by ad group status \(ENABLED, PAUSED, REMOVED\) | +| `limit` | number | No | Maximum number of ad groups to return | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `adGroups` | array | List of ad groups in the campaign | +| ↳ `id` | string | Ad group ID | +| ↳ `name` | string | Ad group name | +| ↳ `status` | string | Ad group status \(ENABLED, PAUSED, REMOVED\) | +| ↳ `type` | string | Ad group type \(SEARCH_STANDARD, DISPLAY_STANDARD, SHOPPING_PRODUCT_ADS\) | +| ↳ `campaignId` | string | Parent campaign ID | +| ↳ `campaignName` | string | Parent campaign name | +| `totalCount` | number | Total number of ad groups returned | + +### `google_ads_ad_performance` + +Get performance metrics for individual ads over a date range + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customerId` | string | Yes | Google Ads customer ID \(numeric, no dashes\) | +| `developerToken` | string | Yes | Google Ads API developer token | +| `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | +| `campaignId` | string | No | Filter by campaign ID | +| `adGroupId` | string | No | Filter by ad group ID | +| `dateRange` | string | No | Predefined date range \(LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, TODAY, YESTERDAY\) | +| `startDate` | string | No | Custom start date in YYYY-MM-DD format | +| `endDate` | string | No | Custom end date in YYYY-MM-DD format | +| `limit` | number | No | Maximum number of results to return | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `ads` | array | Ad performance data broken down by date | +| ↳ `adId` | string | Ad ID | +| ↳ `adGroupId` | string | Parent ad group ID | +| ↳ `adGroupName` | string | Parent ad group name | +| ↳ `campaignId` | string | Parent campaign ID | +| ↳ `campaignName` | string | Parent campaign name | +| ↳ `adType` | string | Ad type \(RESPONSIVE_SEARCH_AD, EXPANDED_TEXT_AD, etc.\) | +| ↳ `impressions` | string | Number of impressions | +| ↳ `clicks` | string | Number of clicks | +| ↳ `costMicros` | string | Cost in micros \(divide by 1,000,000 for currency value\) | +| ↳ `ctr` | number | Click-through rate \(0.0 to 1.0\) | +| ↳ `conversions` | number | Number of conversions | +| ↳ `date` | string | Date for this row \(YYYY-MM-DD\) | +| `totalCount` | number | Total number of result rows | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index a089247e2e..4da47afb4d 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -38,6 +38,7 @@ "gitlab", "gmail", "gong", + "google_ads", "google_bigquery", "google_books", "google_calendar", diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx index c8146a2801..8c2599e231 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx @@ -45,6 +45,7 @@ const SCOPE_DESCRIPTIONS: Record = { '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/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', 'https://www.googleapis.com/auth/devstorage.read_only': 'Read files from Google Cloud Storage', diff --git a/apps/sim/blocks/blocks/google_ads.ts b/apps/sim/blocks/blocks/google_ads.ts new file mode 100644 index 0000000000..cb4422a02b --- /dev/null +++ b/apps/sim/blocks/blocks/google_ads.ts @@ -0,0 +1,307 @@ +import { GoogleAdsIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' + +export const GoogleAdsBlock: BlockConfig = { + type: 'google_ads', + name: 'Google Ads', + description: 'Query campaigns, ad groups, and performance metrics', + longDescription: + 'Connect to Google Ads to list accessible accounts, list campaigns, view ad group details, get performance metrics, and run custom GAQL queries.', + docsLink: 'https://docs.sim.ai/tools/google_ads', + category: 'tools', + bgColor: '#E0E0E0', + icon: GoogleAdsIcon, + authMode: AuthMode.OAuth, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Customers', id: 'list_customers' }, + { label: 'List Campaigns', id: 'list_campaigns' }, + { label: 'Campaign Performance', id: 'campaign_performance' }, + { label: 'List Ad Groups', id: 'list_ad_groups' }, + { label: 'Ad Performance', id: 'ad_performance' }, + { label: 'Custom Query (GAQL)', id: 'search' }, + ], + value: () => 'list_campaigns', + }, + + { + id: 'credential', + title: 'Google Ads Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + required: true, + serviceId: 'google-ads', + requiredScopes: ['https://www.googleapis.com/auth/adwords'], + placeholder: 'Select Google Ads account', + }, + { + id: 'manualCredential', + title: 'Google Ads Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + + { + id: 'developerToken', + title: 'Developer Token', + type: 'short-input', + placeholder: 'Enter your Google Ads API developer token', + required: true, + password: true, + }, + + { + id: 'customerId', + title: 'Customer ID', + type: 'short-input', + placeholder: 'Google Ads customer ID (no dashes)', + condition: { + field: 'operation', + value: 'list_customers', + not: true, + }, + required: { + field: 'operation', + value: 'list_customers', + not: true, + }, + }, + + { + id: 'managerCustomerId', + title: 'Manager Customer ID', + type: 'short-input', + placeholder: 'Manager account ID (optional)', + mode: 'advanced', + condition: { + field: 'operation', + value: 'list_customers', + not: true, + }, + }, + + { + id: 'query', + title: 'GAQL Query', + type: 'long-input', + placeholder: + "SELECT campaign.id, campaign.name, metrics.impressions FROM campaign WHERE campaign.status = 'ENABLED'", + condition: { field: 'operation', value: 'search' }, + required: { field: 'operation', value: 'search' }, + wandConfig: { + enabled: true, + prompt: `Generate a Google Ads Query Language (GAQL) query based on the user's description. +The query should: +- Use valid GAQL syntax +- Include relevant metrics when asking about performance +- Include segments.date with a date range when using metrics +- Be efficient and well-formatted + +Common resources: campaign, ad_group, ad_group_ad, keyword_view, search_term_view +Common metrics: metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.ctr, metrics.conversions +Date ranges: LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH, YESTERDAY + +Examples: +- "active campaigns" -> SELECT campaign.id, campaign.name, campaign.status FROM campaign WHERE campaign.status = 'ENABLED' +- "campaign spend last week" -> SELECT campaign.name, metrics.cost_micros, segments.date FROM campaign WHERE segments.date DURING LAST_7_DAYS AND campaign.status != 'REMOVED' + +Return ONLY the GAQL query - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the query you want to run...', + }, + }, + + { + id: 'campaignId', + title: 'Campaign ID', + type: 'short-input', + placeholder: 'Campaign ID to filter by', + condition: { + field: 'operation', + value: ['campaign_performance', 'list_ad_groups', 'ad_performance'], + }, + required: { field: 'operation', value: 'list_ad_groups' }, + }, + + { + id: 'adGroupId', + title: 'Ad Group ID', + type: 'short-input', + placeholder: 'Ad group ID to filter by', + mode: 'advanced', + condition: { field: 'operation', value: 'ad_performance' }, + }, + + { + id: 'status', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All (except removed)', id: '' }, + { label: 'Enabled', id: 'ENABLED' }, + { label: 'Paused', id: 'PAUSED' }, + ], + mode: 'advanced', + condition: { field: 'operation', value: ['list_campaigns', 'list_ad_groups'] }, + }, + + { + id: 'dateRange', + title: 'Date Range', + type: 'dropdown', + options: [ + { label: 'Last 30 Days', id: 'LAST_30_DAYS' }, + { label: 'Last 7 Days', id: 'LAST_7_DAYS' }, + { label: 'Today', id: 'TODAY' }, + { label: 'Yesterday', id: 'YESTERDAY' }, + { label: 'This Month', id: 'THIS_MONTH' }, + { label: 'Last Month', id: 'LAST_MONTH' }, + { label: 'Custom', id: 'CUSTOM' }, + ], + condition: { field: 'operation', value: ['campaign_performance', 'ad_performance'] }, + value: () => 'LAST_30_DAYS', + }, + + { + id: 'startDate', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'dateRange', value: 'CUSTOM' }, + required: { field: 'dateRange', value: 'CUSTOM' }, + }, + + { + id: 'endDate', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'dateRange', value: 'CUSTOM' }, + required: { field: 'dateRange', value: 'CUSTOM' }, + }, + + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: 'Max results per page (default 10000)', + mode: 'advanced', + condition: { field: 'operation', value: 'search' }, + }, + + { + id: 'pageToken', + title: 'Page Token', + type: 'short-input', + placeholder: 'Pagination token', + mode: 'advanced', + condition: { field: 'operation', value: 'search' }, + }, + + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Maximum results to return', + mode: 'advanced', + condition: { + field: 'operation', + value: ['list_campaigns', 'list_ad_groups', 'ad_performance'], + }, + }, + ], + tools: { + access: [ + 'google_ads_list_customers', + 'google_ads_search', + 'google_ads_list_campaigns', + 'google_ads_campaign_performance', + 'google_ads_list_ad_groups', + 'google_ads_ad_performance', + ], + config: { + tool: (params) => `google_ads_${params.operation}`, + params: (params) => { + const { oauthCredential, dateRange, pageSize, limit, ...rest } = params + + const result: Record = { + ...rest, + oauthCredential, + } + + if (dateRange && dateRange !== 'CUSTOM') { + result.dateRange = dateRange + } + + if (pageSize !== undefined && pageSize !== '') { + result.pageSize = Number(pageSize) + } + + if (limit !== undefined && limit !== '') { + result.limit = Number(limit) + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Google Ads OAuth credential' }, + developerToken: { type: 'string', description: 'Google Ads API developer token' }, + customerId: { type: 'string', description: 'Google Ads customer ID (numeric, no dashes)' }, + managerCustomerId: { type: 'string', description: 'Manager account customer ID' }, + query: { type: 'string', description: 'GAQL query to execute' }, + campaignId: { type: 'string', description: 'Campaign ID to filter by' }, + adGroupId: { type: 'string', description: 'Ad group ID to filter by' }, + status: { type: 'string', description: 'Status filter (ENABLED, PAUSED)' }, + dateRange: { type: 'string', description: 'Date range for performance queries' }, + startDate: { type: 'string', description: 'Custom start date (YYYY-MM-DD)' }, + endDate: { type: 'string', description: 'Custom end date (YYYY-MM-DD)' }, + pageSize: { type: 'number', description: 'Max results per page (max 10000)' }, + pageToken: { type: 'string', description: 'Pagination token' }, + limit: { type: 'number', description: 'Maximum results to return' }, + }, + outputs: { + customerIds: { + type: 'json', + description: 'List of accessible customer IDs (list_customers)', + }, + results: { + type: 'json', + description: 'Query results (search)', + }, + campaigns: { + type: 'json', + description: 'Campaign data (list_campaigns, campaign_performance)', + }, + adGroups: { + type: 'json', + description: 'Ad group data (list_ad_groups)', + }, + ads: { + type: 'json', + description: 'Ad performance data (ad_performance)', + }, + totalCount: { + type: 'number', + description: 'Total number of results', + }, + totalResultsCount: { + type: 'number', + description: 'Total results count (search)', + }, + nextPageToken: { + type: 'string', + description: 'Token for next page of results', + }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index eff25ffb1d..358508aede 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -44,6 +44,7 @@ import { GitLabBlock } from '@/blocks/blocks/gitlab' 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 { GoogleBigQueryBlock } from '@/blocks/blocks/google_bigquery' import { GoogleBooksBlock } from '@/blocks/blocks/google_books' import { GoogleCalendarBlock, GoogleCalendarV2Block } from '@/blocks/blocks/google_calendar' @@ -233,6 +234,7 @@ export const registry: Record = { gmail_v2: GmailV2Block, google_calendar: GoogleCalendarBlock, google_calendar_v2: GoogleCalendarV2Block, + google_ads: GoogleAdsBlock, google_books: GoogleBooksBlock, google_docs: GoogleDocsBlock, google_drive: GoogleDriveBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 9e68974089..f8ffe44ffc 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -3464,6 +3464,27 @@ export const ResendIcon = (props: SVGProps) => ( ) +export const GoogleAdsIcon = (props: SVGProps) => ( + + + + + + + + + +) + export const GoogleBigQueryIcon = (props: SVGProps) => ( { + 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-bigquery', clientId: env.GOOGLE_CLIENT_ID as string, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 2b9a96aca8..0eb7111be4 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -8,6 +8,7 @@ import { DropboxIcon, GithubIcon, GmailIcon, + GoogleAdsIcon, GoogleBigQueryIcon, GoogleCalendarIcon, GoogleDocsIcon, @@ -121,6 +122,14 @@ export const OAUTH_PROVIDERS: Record = { baseProviderIcon: GoogleIcon, scopes: ['https://www.googleapis.com/auth/calendar'], }, + 'google-ads': { + name: 'Google Ads', + description: 'Query campaigns, ad groups, and performance metrics in Google Ads.', + providerId: 'google-ads', + icon: GoogleAdsIcon, + baseProviderIcon: GoogleIcon, + scopes: ['https://www.googleapis.com/auth/adwords'], + }, 'google-bigquery': { name: 'Google BigQuery', description: 'Query, list, and insert data in Google BigQuery.', diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 0da86f06fd..725b0f2c36 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -7,6 +7,7 @@ export type OAuthProvider = | 'google-docs' | 'google-sheets' | 'google-calendar' + | 'google-ads' | 'google-bigquery' | 'google-tasks' | 'google-vault' @@ -54,6 +55,7 @@ export type OAuthService = | 'google-docs' | 'google-sheets' | 'google-calendar' + | 'google-ads' | 'google-bigquery' | 'google-tasks' | 'google-vault' diff --git a/apps/sim/tools/google_ads/ad_performance.ts b/apps/sim/tools/google_ads/ad_performance.ts new file mode 100644 index 0000000000..0e9eddd43d --- /dev/null +++ b/apps/sim/tools/google_ads/ad_performance.ts @@ -0,0 +1,203 @@ +import type { + GoogleAdsAdPerformanceParams, + GoogleAdsAdPerformanceResponse, +} from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsAdPerformanceTool: ToolConfig< + GoogleAdsAdPerformanceParams, + GoogleAdsAdPerformanceResponse +> = { + id: 'google_ads_ad_performance', + name: 'Google Ads Ad Performance', + description: 'Get performance metrics for individual ads over a date range', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + customerId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + managerCustomerId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Manager account customer ID (if accessing via manager account)', + }, + campaignId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by campaign ID', + }, + adGroupId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by ad group ID', + }, + dateRange: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Predefined date range (LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, TODAY, YESTERDAY)', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom start date in YYYY-MM-DD format', + }, + endDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom end date in YYYY-MM-DD format', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return', + }, + }, + + request: { + url: (params) => + `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + method: 'POST', + headers: (params) => { + const headers: Record = { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'developer-token': params.developerToken, + } + if (params.managerCustomerId) { + headers['login-customer-id'] = params.managerCustomerId + } + return headers + }, + body: (params) => { + let query = + 'SELECT ad_group_ad.ad.id, ad_group.id, ad_group.name, campaign.id, campaign.name, ad_group_ad.ad.type, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.ctr, metrics.conversions, segments.date FROM ad_group_ad' + + const conditions: string[] = ["ad_group_ad.status != 'REMOVED'"] + + if (params.campaignId) { + conditions.push(`campaign.id = ${params.campaignId}`) + } + + if (params.adGroupId) { + conditions.push(`ad_group.id = ${params.adGroupId}`) + } + + if (params.startDate && params.endDate) { + conditions.push(`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`) + } else { + const dateRange = params.dateRange || 'LAST_30_DAYS' + conditions.push(`segments.date DURING ${dateRange}`) + } + + query += ` WHERE ${conditions.join(' AND ')}` + query += ' ORDER BY metrics.impressions DESC' + + if (params.limit) { + query += ` LIMIT ${params.limit}` + } + + return { query } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { ads: [], totalCount: 0 }, + error: errorMessage, + } + } + + const results = data.results ?? [] + const ads = results.map((r: Record) => ({ + adId: r.adGroupAd?.ad?.id ?? '', + adGroupId: r.adGroup?.id ?? '', + adGroupName: r.adGroup?.name ?? null, + campaignId: r.campaign?.id ?? '', + campaignName: r.campaign?.name ?? null, + adType: r.adGroupAd?.ad?.type ?? null, + impressions: r.metrics?.impressions ?? '0', + clicks: r.metrics?.clicks ?? '0', + costMicros: r.metrics?.costMicros ?? '0', + ctr: r.metrics?.ctr ?? null, + conversions: r.metrics?.conversions ?? null, + date: r.segments?.date ?? null, + })) + + return { + success: true, + output: { + ads, + totalCount: ads.length, + }, + } + }, + + outputs: { + ads: { + type: 'array', + description: 'Ad performance data broken down by date', + items: { + type: 'object', + properties: { + adId: { type: 'string', description: 'Ad ID' }, + adGroupId: { type: 'string', description: 'Parent ad group ID' }, + adGroupName: { type: 'string', description: 'Parent ad group name' }, + campaignId: { type: 'string', description: 'Parent campaign ID' }, + campaignName: { type: 'string', description: 'Parent campaign name' }, + adType: { + type: 'string', + description: 'Ad type (RESPONSIVE_SEARCH_AD, EXPANDED_TEXT_AD, etc.)', + }, + impressions: { type: 'string', description: 'Number of impressions' }, + clicks: { type: 'string', description: 'Number of clicks' }, + costMicros: { + type: 'string', + description: 'Cost in micros (divide by 1,000,000 for currency value)', + }, + ctr: { type: 'number', description: 'Click-through rate (0.0 to 1.0)' }, + conversions: { type: 'number', description: 'Number of conversions' }, + date: { type: 'string', description: 'Date for this row (YYYY-MM-DD)' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of result rows', + }, + }, +} diff --git a/apps/sim/tools/google_ads/campaign_performance.ts b/apps/sim/tools/google_ads/campaign_performance.ts new file mode 100644 index 0000000000..590581fd4e --- /dev/null +++ b/apps/sim/tools/google_ads/campaign_performance.ts @@ -0,0 +1,174 @@ +import type { + GoogleAdsCampaignPerformanceParams, + GoogleAdsCampaignPerformanceResponse, +} from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsCampaignPerformanceTool: ToolConfig< + GoogleAdsCampaignPerformanceParams, + GoogleAdsCampaignPerformanceResponse +> = { + id: 'google_ads_campaign_performance', + name: 'Google Ads Campaign Performance', + description: 'Get performance metrics for Google Ads campaigns over a date range', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + customerId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + managerCustomerId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Manager account customer ID (if accessing via manager account)', + }, + campaignId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by specific campaign ID', + }, + dateRange: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Predefined date range (LAST_7_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, TODAY, YESTERDAY)', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom start date in YYYY-MM-DD format', + }, + endDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom end date in YYYY-MM-DD format', + }, + }, + + request: { + url: (params) => + `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + method: 'POST', + headers: (params) => { + const headers: Record = { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'developer-token': params.developerToken, + } + if (params.managerCustomerId) { + headers['login-customer-id'] = params.managerCustomerId + } + return headers + }, + body: (params) => { + let query = + 'SELECT campaign.id, campaign.name, campaign.status, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.ctr, metrics.conversions, segments.date FROM campaign' + + const conditions: string[] = ["campaign.status != 'REMOVED'"] + + if (params.campaignId) { + conditions.push(`campaign.id = ${params.campaignId}`) + } + + if (params.startDate && params.endDate) { + conditions.push(`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`) + } else { + const dateRange = params.dateRange || 'LAST_30_DAYS' + conditions.push(`segments.date DURING ${dateRange}`) + } + + query += ` WHERE ${conditions.join(' AND ')}` + query += ' ORDER BY metrics.impressions DESC' + + return { query } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { campaigns: [], totalCount: 0 }, + error: errorMessage, + } + } + + const results = data.results ?? [] + const campaigns = results.map((r: Record) => ({ + id: r.campaign?.id ?? '', + name: r.campaign?.name ?? '', + status: r.campaign?.status ?? '', + impressions: r.metrics?.impressions ?? '0', + clicks: r.metrics?.clicks ?? '0', + costMicros: r.metrics?.costMicros ?? '0', + ctr: r.metrics?.ctr ?? null, + conversions: r.metrics?.conversions ?? null, + date: r.segments?.date ?? null, + })) + + return { + success: true, + output: { + campaigns, + totalCount: campaigns.length, + }, + } + }, + + outputs: { + campaigns: { + type: 'array', + description: 'Campaign performance data broken down by date', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Campaign ID' }, + name: { type: 'string', description: 'Campaign name' }, + status: { type: 'string', description: 'Campaign status' }, + impressions: { type: 'string', description: 'Number of impressions' }, + clicks: { type: 'string', description: 'Number of clicks' }, + costMicros: { + type: 'string', + description: 'Cost in micros (divide by 1,000,000 for currency value)', + }, + ctr: { type: 'number', description: 'Click-through rate (0.0 to 1.0)' }, + conversions: { type: 'number', description: 'Number of conversions' }, + date: { type: 'string', description: 'Date for this row (YYYY-MM-DD)' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of result rows', + }, + }, +} diff --git a/apps/sim/tools/google_ads/index.ts b/apps/sim/tools/google_ads/index.ts new file mode 100644 index 0000000000..d61d7a6af2 --- /dev/null +++ b/apps/sim/tools/google_ads/index.ts @@ -0,0 +1,15 @@ +import { googleAdsAdPerformanceTool } from '@/tools/google_ads/ad_performance' +import { googleAdsCampaignPerformanceTool } from '@/tools/google_ads/campaign_performance' +import { googleAdsListAdGroupsTool } from '@/tools/google_ads/list_ad_groups' +import { googleAdsListCampaignsTool } from '@/tools/google_ads/list_campaigns' +import { googleAdsListCustomersTool } from '@/tools/google_ads/list_customers' +import { googleAdsSearchTool } from '@/tools/google_ads/search' + +export { + googleAdsAdPerformanceTool, + googleAdsCampaignPerformanceTool, + googleAdsListAdGroupsTool, + googleAdsListCampaignsTool, + googleAdsListCustomersTool, + googleAdsSearchTool, +} diff --git a/apps/sim/tools/google_ads/list_ad_groups.ts b/apps/sim/tools/google_ads/list_ad_groups.ts new file mode 100644 index 0000000000..b617138284 --- /dev/null +++ b/apps/sim/tools/google_ads/list_ad_groups.ts @@ -0,0 +1,160 @@ +import type { + GoogleAdsListAdGroupsParams, + GoogleAdsListAdGroupsResponse, +} from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsListAdGroupsTool: ToolConfig< + GoogleAdsListAdGroupsParams, + GoogleAdsListAdGroupsResponse +> = { + id: 'google_ads_list_ad_groups', + name: 'List Google Ads Ad Groups', + description: 'List ad groups in a Google Ads campaign', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + customerId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + managerCustomerId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Manager account customer ID (if accessing via manager account)', + }, + campaignId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Campaign ID to list ad groups for', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by ad group status (ENABLED, PAUSED, REMOVED)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of ad groups to return', + }, + }, + + request: { + url: (params) => + `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + method: 'POST', + headers: (params) => { + const headers: Record = { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'developer-token': params.developerToken, + } + if (params.managerCustomerId) { + headers['login-customer-id'] = params.managerCustomerId + } + return headers + }, + body: (params) => { + let query = + 'SELECT ad_group.id, ad_group.name, ad_group.status, ad_group.type, campaign.id, campaign.name FROM ad_group' + + const conditions: string[] = [`campaign.id = ${params.campaignId}`] + + if (params.status) { + conditions.push(`ad_group.status = '${params.status}'`) + } else { + conditions.push("ad_group.status != 'REMOVED'") + } + + query += ` WHERE ${conditions.join(' AND ')}` + query += ' ORDER BY ad_group.name' + + if (params.limit) { + query += ` LIMIT ${params.limit}` + } + + return { query } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { adGroups: [], totalCount: 0 }, + error: errorMessage, + } + } + + const results = data.results ?? [] + const adGroups = results.map((r: Record) => ({ + id: r.adGroup?.id ?? '', + name: r.adGroup?.name ?? '', + status: r.adGroup?.status ?? '', + type: r.adGroup?.type ?? null, + campaignId: r.campaign?.id ?? '', + campaignName: r.campaign?.name ?? null, + })) + + return { + success: true, + output: { + adGroups, + totalCount: adGroups.length, + }, + } + }, + + outputs: { + adGroups: { + type: 'array', + description: 'List of ad groups in the campaign', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Ad group ID' }, + name: { type: 'string', description: 'Ad group name' }, + status: { type: 'string', description: 'Ad group status (ENABLED, PAUSED, REMOVED)' }, + type: { + type: 'string', + description: 'Ad group type (SEARCH_STANDARD, DISPLAY_STANDARD, SHOPPING_PRODUCT_ADS)', + }, + campaignId: { type: 'string', description: 'Parent campaign ID' }, + campaignName: { type: 'string', description: 'Parent campaign name' }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of ad groups returned', + }, + }, +} diff --git a/apps/sim/tools/google_ads/list_campaigns.ts b/apps/sim/tools/google_ads/list_campaigns.ts new file mode 100644 index 0000000000..d4a0c952f0 --- /dev/null +++ b/apps/sim/tools/google_ads/list_campaigns.ts @@ -0,0 +1,162 @@ +import type { + GoogleAdsListCampaignsParams, + GoogleAdsListCampaignsResponse, +} from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsListCampaignsTool: ToolConfig< + GoogleAdsListCampaignsParams, + GoogleAdsListCampaignsResponse +> = { + id: 'google_ads_list_campaigns', + name: 'List Google Ads Campaigns', + description: 'List campaigns in a Google Ads account with optional status filtering', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + customerId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + managerCustomerId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Manager account customer ID (if accessing via manager account)', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by campaign status (ENABLED, PAUSED, REMOVED)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of campaigns to return', + }, + }, + + request: { + url: (params) => + `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + method: 'POST', + headers: (params) => { + const headers: Record = { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'developer-token': params.developerToken, + } + if (params.managerCustomerId) { + headers['login-customer-id'] = params.managerCustomerId + } + return headers + }, + body: (params) => { + let query = + 'SELECT campaign.id, campaign.name, campaign.status, campaign.advertising_channel_type, campaign.start_date, campaign.end_date, campaign_budget.amount_micros FROM campaign' + + const conditions: string[] = [] + if (params.status) { + conditions.push(`campaign.status = '${params.status}'`) + } else { + conditions.push("campaign.status != 'REMOVED'") + } + + if (conditions.length > 0) { + query += ` WHERE ${conditions.join(' AND ')}` + } + + query += ' ORDER BY campaign.name' + + if (params.limit) { + query += ` LIMIT ${params.limit}` + } + + return { query } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { campaigns: [], totalCount: 0 }, + error: errorMessage, + } + } + + const results = data.results ?? [] + const campaigns = results.map((r: Record) => ({ + id: r.campaign?.id ?? '', + name: r.campaign?.name ?? '', + status: r.campaign?.status ?? '', + channelType: r.campaign?.advertisingChannelType ?? null, + startDate: r.campaign?.startDate ?? null, + endDate: r.campaign?.endDate ?? null, + budgetAmountMicros: r.campaignBudget?.amountMicros ?? null, + })) + + return { + success: true, + output: { + campaigns, + totalCount: campaigns.length, + }, + } + }, + + outputs: { + campaigns: { + type: 'array', + description: 'List of campaigns in the account', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Campaign ID' }, + name: { type: 'string', description: 'Campaign name' }, + status: { type: 'string', description: 'Campaign status (ENABLED, PAUSED, REMOVED)' }, + channelType: { + type: 'string', + description: + 'Advertising channel type (SEARCH, DISPLAY, SHOPPING, VIDEO, PERFORMANCE_MAX)', + }, + startDate: { type: 'string', description: 'Campaign start date (YYYY-MM-DD)' }, + endDate: { type: 'string', description: 'Campaign end date (YYYY-MM-DD)' }, + budgetAmountMicros: { + type: 'string', + description: 'Daily budget in micros (divide by 1,000,000 for currency value)', + }, + }, + }, + }, + totalCount: { + type: 'number', + description: 'Total number of campaigns returned', + }, + }, +} diff --git a/apps/sim/tools/google_ads/list_customers.ts b/apps/sim/tools/google_ads/list_customers.ts new file mode 100644 index 0000000000..9d764b8691 --- /dev/null +++ b/apps/sim/tools/google_ads/list_customers.ts @@ -0,0 +1,84 @@ +import type { + GoogleAdsListCustomersParams, + GoogleAdsListCustomersResponse, +} from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsListCustomersTool: ToolConfig< + GoogleAdsListCustomersParams, + GoogleAdsListCustomersResponse +> = { + id: 'google_ads_list_customers', + name: 'List Google Ads Customers', + description: 'List all Google Ads customer accounts accessible by the authenticated user', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + }, + + request: { + url: 'https://googleads.googleapis.com/v19/customers:listAccessibleCustomers', + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'developer-token': params.developerToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { customerIds: [], totalCount: 0 }, + error: errorMessage, + } + } + + const resourceNames: string[] = data.resourceNames ?? [] + const customerIds = resourceNames.map((rn: string) => rn.replace('customers/', '')) + + return { + success: true, + output: { + customerIds, + totalCount: customerIds.length, + }, + } + }, + + outputs: { + customerIds: { + type: 'array', + description: 'List of accessible customer IDs', + items: { + type: 'string', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + }, + totalCount: { + type: 'number', + description: 'Total number of accessible customer accounts', + }, + }, +} diff --git a/apps/sim/tools/google_ads/search.ts b/apps/sim/tools/google_ads/search.ts new file mode 100644 index 0000000000..9b66bdedb3 --- /dev/null +++ b/apps/sim/tools/google_ads/search.ts @@ -0,0 +1,131 @@ +import type { GoogleAdsSearchParams, GoogleAdsSearchResponse } from '@/tools/google_ads/types' +import type { ToolConfig } from '@/tools/types' + +export const googleAdsSearchTool: ToolConfig = { + id: 'google_ads_search', + name: 'Google Ads Search (GAQL)', + description: 'Run a custom Google Ads Query Language (GAQL) query', + version: '1.0.0', + + oauth: { + required: true, + provider: 'google-ads', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Google Ads API', + }, + customerId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Google Ads customer ID (numeric, no dashes)', + }, + developerToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Google Ads API developer token', + }, + managerCustomerId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Manager account customer ID (if accessing via manager account)', + }, + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'GAQL query to execute', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results per page (max 10000)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token for pagination', + }, + }, + + request: { + url: (params) => + `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + method: 'POST', + headers: (params) => { + const headers: Record = { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'developer-token': params.developerToken, + } + if (params.managerCustomerId) { + headers['login-customer-id'] = params.managerCustomerId + } + return headers + }, + body: (params) => { + const body: Record = { + query: params.query, + returnTotalResultsCount: true, + } + if (params.pageSize) { + body.pageSize = params.pageSize + } + if (params.pageToken) { + body.pageToken = params.pageToken + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = + data?.error?.message ?? data?.error?.details?.[0]?.errors?.[0]?.message ?? 'Unknown error' + return { + success: false, + output: { + results: [], + totalResultsCount: null, + nextPageToken: null, + }, + error: errorMessage, + } + } + + return { + success: true, + output: { + results: data.results ?? [], + totalResultsCount: data.totalResultsCount ? Number(data.totalResultsCount) : null, + nextPageToken: data.nextPageToken ?? null, + }, + } + }, + + outputs: { + results: { + type: 'json', + description: 'Array of result objects from the GAQL query', + }, + totalResultsCount: { + type: 'number', + description: 'Total number of matching results', + }, + nextPageToken: { + type: 'string', + description: 'Token for the next page of results', + }, + }, +} diff --git a/apps/sim/tools/google_ads/types.ts b/apps/sim/tools/google_ads/types.ts new file mode 100644 index 0000000000..dde7c8e7c0 --- /dev/null +++ b/apps/sim/tools/google_ads/types.ts @@ -0,0 +1,135 @@ +import type { ToolResponse } from '@/tools/types' + +export interface GoogleAdsBaseParams { + accessToken: string + customerId: string + developerToken: string + managerCustomerId?: string +} + +export interface GoogleAdsListCustomersParams { + accessToken: string + developerToken: string +} + +export interface GoogleAdsSearchParams extends GoogleAdsBaseParams { + query: string + pageSize?: number + pageToken?: string +} + +export interface GoogleAdsListCampaignsParams extends GoogleAdsBaseParams { + status?: string + limit?: number +} + +export interface GoogleAdsCampaignPerformanceParams extends GoogleAdsBaseParams { + campaignId?: string + dateRange?: string + startDate?: string + endDate?: string +} + +export interface GoogleAdsListAdGroupsParams extends GoogleAdsBaseParams { + campaignId: string + status?: string + limit?: number +} + +export interface GoogleAdsAdPerformanceParams extends GoogleAdsBaseParams { + campaignId?: string + adGroupId?: string + dateRange?: string + startDate?: string + endDate?: string + limit?: number +} + +export interface GoogleAdsListCustomersResponse extends ToolResponse { + output: { + customerIds: string[] + totalCount: number + } +} + +export interface GoogleAdsSearchResponse extends ToolResponse { + output: { + results: Record[] + totalResultsCount: number | null + nextPageToken: string | null + } +} + +export interface GoogleAdsCampaign { + id: string + name: string + status: string + channelType: string | null + startDate: string | null + endDate: string | null + budgetAmountMicros: string | null +} + +export interface GoogleAdsListCampaignsResponse extends ToolResponse { + output: { + campaigns: GoogleAdsCampaign[] + totalCount: number + } +} + +export interface GoogleAdsCampaignPerformance { + id: string + name: string + status: string + impressions: string + clicks: string + costMicros: string + ctr: number | null + conversions: number | null + date: string | null +} + +export interface GoogleAdsCampaignPerformanceResponse extends ToolResponse { + output: { + campaigns: GoogleAdsCampaignPerformance[] + totalCount: number + } +} + +export interface GoogleAdsAdGroup { + id: string + name: string + status: string + type: string | null + campaignId: string + campaignName: string | null +} + +export interface GoogleAdsListAdGroupsResponse extends ToolResponse { + output: { + adGroups: GoogleAdsAdGroup[] + totalCount: number + } +} + +export interface GoogleAdsAdPerformance { + adId: string + adGroupId: string + adGroupName: string | null + campaignId: string + campaignName: string | null + adType: string | null + impressions: string + clicks: string + costMicros: string + ctr: number | null + conversions: number | null + date: string | null +} + +export interface GoogleAdsAdPerformanceResponse extends ToolResponse { + output: { + ads: GoogleAdsAdPerformance[] + totalCount: number + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index d8302770c3..1102239711 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -650,6 +650,14 @@ import { gongLookupPhoneTool, } from '@/tools/gong' import { googleSearchTool } from '@/tools/google' +import { + googleAdsAdPerformanceTool, + googleAdsCampaignPerformanceTool, + googleAdsListAdGroupsTool, + googleAdsListCampaignsTool, + googleAdsListCustomersTool, + googleAdsSearchTool, +} from '@/tools/google_ads' import { googleBigQueryGetTableTool, googleBigQueryInsertRowsTool, @@ -3652,6 +3660,12 @@ export const tools: Record = { wordpress_list_users: wordpressListUsersTool, wordpress_get_user: wordpressGetUserTool, wordpress_search_content: wordpressSearchContentTool, + google_ads_list_customers: googleAdsListCustomersTool, + google_ads_search: googleAdsSearchTool, + google_ads_list_campaigns: googleAdsListCampaignsTool, + google_ads_campaign_performance: googleAdsCampaignPerformanceTool, + google_ads_list_ad_groups: googleAdsListAdGroupsTool, + google_ads_ad_performance: googleAdsAdPerformanceTool, google_bigquery_query: googleBigQueryQueryTool, google_bigquery_list_datasets: googleBigQueryListDatasetsTool, google_bigquery_list_tables: googleBigQueryListTablesTool, From 1b0fb527d8d4917be948429e5fd61f31852d1a60 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Feb 2026 17:26:16 -0800 Subject: [PATCH 2/5] fix(google-ads): add input validation for GAQL query parameters --- apps/sim/tools/google_ads/ad_performance.ts | 17 ++++--- .../tools/google_ads/campaign_performance.ts | 15 ++++-- apps/sim/tools/google_ads/list_ad_groups.ts | 12 +++-- apps/sim/tools/google_ads/list_campaigns.ts | 9 ++-- apps/sim/tools/google_ads/search.ts | 7 ++- apps/sim/tools/google_ads/types.ts | 47 +++++++++++++++++++ 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/apps/sim/tools/google_ads/ad_performance.ts b/apps/sim/tools/google_ads/ad_performance.ts index 0e9eddd43d..0595801428 100644 --- a/apps/sim/tools/google_ads/ad_performance.ts +++ b/apps/sim/tools/google_ads/ad_performance.ts @@ -2,6 +2,7 @@ import type { GoogleAdsAdPerformanceParams, GoogleAdsAdPerformanceResponse, } from '@/tools/google_ads/types' +import { validateDate, validateDateRange, validateNumericId } from '@/tools/google_ads/types' import type { ToolConfig } from '@/tools/types' export const googleAdsAdPerformanceTool: ToolConfig< @@ -83,8 +84,10 @@ export const googleAdsAdPerformanceTool: ToolConfig< }, request: { - url: (params) => - `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + url: (params) => { + const customerId = validateNumericId(params.customerId, 'customerId') + return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + }, method: 'POST', headers: (params) => { const headers: Record = { @@ -104,17 +107,19 @@ export const googleAdsAdPerformanceTool: ToolConfig< const conditions: string[] = ["ad_group_ad.status != 'REMOVED'"] if (params.campaignId) { - conditions.push(`campaign.id = ${params.campaignId}`) + conditions.push(`campaign.id = ${validateNumericId(params.campaignId, 'campaignId')}`) } if (params.adGroupId) { - conditions.push(`ad_group.id = ${params.adGroupId}`) + conditions.push(`ad_group.id = ${validateNumericId(params.adGroupId, 'adGroupId')}`) } if (params.startDate && params.endDate) { - conditions.push(`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`) + const start = validateDate(params.startDate, 'startDate') + const end = validateDate(params.endDate, 'endDate') + conditions.push(`segments.date BETWEEN '${start}' AND '${end}'`) } else { - const dateRange = params.dateRange || 'LAST_30_DAYS' + const dateRange = validateDateRange(params.dateRange || 'LAST_30_DAYS') conditions.push(`segments.date DURING ${dateRange}`) } diff --git a/apps/sim/tools/google_ads/campaign_performance.ts b/apps/sim/tools/google_ads/campaign_performance.ts index 590581fd4e..cb00cda444 100644 --- a/apps/sim/tools/google_ads/campaign_performance.ts +++ b/apps/sim/tools/google_ads/campaign_performance.ts @@ -2,6 +2,7 @@ import type { GoogleAdsCampaignPerformanceParams, GoogleAdsCampaignPerformanceResponse, } from '@/tools/google_ads/types' +import { validateDate, validateDateRange, validateNumericId } from '@/tools/google_ads/types' import type { ToolConfig } from '@/tools/types' export const googleAdsCampaignPerformanceTool: ToolConfig< @@ -71,8 +72,10 @@ export const googleAdsCampaignPerformanceTool: ToolConfig< }, request: { - url: (params) => - `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + url: (params) => { + const customerId = validateNumericId(params.customerId, 'customerId') + return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + }, method: 'POST', headers: (params) => { const headers: Record = { @@ -92,13 +95,15 @@ export const googleAdsCampaignPerformanceTool: ToolConfig< const conditions: string[] = ["campaign.status != 'REMOVED'"] if (params.campaignId) { - conditions.push(`campaign.id = ${params.campaignId}`) + conditions.push(`campaign.id = ${validateNumericId(params.campaignId, 'campaignId')}`) } if (params.startDate && params.endDate) { - conditions.push(`segments.date BETWEEN '${params.startDate}' AND '${params.endDate}'`) + const start = validateDate(params.startDate, 'startDate') + const end = validateDate(params.endDate, 'endDate') + conditions.push(`segments.date BETWEEN '${start}' AND '${end}'`) } else { - const dateRange = params.dateRange || 'LAST_30_DAYS' + const dateRange = validateDateRange(params.dateRange || 'LAST_30_DAYS') conditions.push(`segments.date DURING ${dateRange}`) } diff --git a/apps/sim/tools/google_ads/list_ad_groups.ts b/apps/sim/tools/google_ads/list_ad_groups.ts index b617138284..b4606d2e3c 100644 --- a/apps/sim/tools/google_ads/list_ad_groups.ts +++ b/apps/sim/tools/google_ads/list_ad_groups.ts @@ -2,6 +2,7 @@ import type { GoogleAdsListAdGroupsParams, GoogleAdsListAdGroupsResponse, } from '@/tools/google_ads/types' +import { validateNumericId, validateStatus } from '@/tools/google_ads/types' import type { ToolConfig } from '@/tools/types' export const googleAdsListAdGroupsTool: ToolConfig< @@ -64,8 +65,10 @@ export const googleAdsListAdGroupsTool: ToolConfig< }, request: { - url: (params) => - `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + url: (params) => { + const customerId = validateNumericId(params.customerId, 'customerId') + return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + }, method: 'POST', headers: (params) => { const headers: Record = { @@ -82,10 +85,11 @@ export const googleAdsListAdGroupsTool: ToolConfig< let query = 'SELECT ad_group.id, ad_group.name, ad_group.status, ad_group.type, campaign.id, campaign.name FROM ad_group' - const conditions: string[] = [`campaign.id = ${params.campaignId}`] + const campaignId = validateNumericId(params.campaignId, 'campaignId') + const conditions: string[] = [`campaign.id = ${campaignId}`] if (params.status) { - conditions.push(`ad_group.status = '${params.status}'`) + conditions.push(`ad_group.status = '${validateStatus(params.status)}'`) } else { conditions.push("ad_group.status != 'REMOVED'") } diff --git a/apps/sim/tools/google_ads/list_campaigns.ts b/apps/sim/tools/google_ads/list_campaigns.ts index d4a0c952f0..fc41d62833 100644 --- a/apps/sim/tools/google_ads/list_campaigns.ts +++ b/apps/sim/tools/google_ads/list_campaigns.ts @@ -2,6 +2,7 @@ import type { GoogleAdsListCampaignsParams, GoogleAdsListCampaignsResponse, } from '@/tools/google_ads/types' +import { validateNumericId, validateStatus } from '@/tools/google_ads/types' import type { ToolConfig } from '@/tools/types' export const googleAdsListCampaignsTool: ToolConfig< @@ -58,8 +59,10 @@ export const googleAdsListCampaignsTool: ToolConfig< }, request: { - url: (params) => - `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + url: (params) => { + const customerId = validateNumericId(params.customerId, 'customerId') + return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + }, method: 'POST', headers: (params) => { const headers: Record = { @@ -78,7 +81,7 @@ export const googleAdsListCampaignsTool: ToolConfig< const conditions: string[] = [] if (params.status) { - conditions.push(`campaign.status = '${params.status}'`) + conditions.push(`campaign.status = '${validateStatus(params.status)}'`) } else { conditions.push("campaign.status != 'REMOVED'") } diff --git a/apps/sim/tools/google_ads/search.ts b/apps/sim/tools/google_ads/search.ts index 9b66bdedb3..c9e774d64f 100644 --- a/apps/sim/tools/google_ads/search.ts +++ b/apps/sim/tools/google_ads/search.ts @@ -1,4 +1,5 @@ import type { GoogleAdsSearchParams, GoogleAdsSearchResponse } from '@/tools/google_ads/types' +import { validateNumericId } from '@/tools/google_ads/types' import type { ToolConfig } from '@/tools/types' export const googleAdsSearchTool: ToolConfig = { @@ -58,8 +59,10 @@ export const googleAdsSearchTool: ToolConfig - `https://googleads.googleapis.com/v19/customers/${params.customerId}/googleAds:search`, + url: (params) => { + const customerId = validateNumericId(params.customerId, 'customerId') + return `https://googleads.googleapis.com/v19/customers/${customerId}/googleAds:search` + }, method: 'POST', headers: (params) => { const headers: Record = { diff --git a/apps/sim/tools/google_ads/types.ts b/apps/sim/tools/google_ads/types.ts index dde7c8e7c0..232c93dedf 100644 --- a/apps/sim/tools/google_ads/types.ts +++ b/apps/sim/tools/google_ads/types.ts @@ -1,5 +1,52 @@ import type { ToolResponse } from '@/tools/types' +const NUMERIC_ID_REGEX = /^\d+$/ +const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ +const VALID_STATUSES = new Set(['ENABLED', 'PAUSED', 'REMOVED']) +const VALID_DATE_RANGES = new Set([ + 'LAST_7_DAYS', + 'LAST_30_DAYS', + 'THIS_MONTH', + 'LAST_MONTH', + 'TODAY', + 'YESTERDAY', +]) + +/** Validates that a value is a numeric ID (digits only). */ +export function validateNumericId(value: string, fieldName: string): string { + const cleaned = value.replace(/-/g, '') + if (!NUMERIC_ID_REGEX.test(cleaned)) { + throw new Error(`${fieldName} must be numeric (digits only), got: ${value}`) + } + return cleaned +} + +/** Validates that a status value is a known Google Ads status. */ +export function validateStatus(value: string): string { + if (!VALID_STATUSES.has(value)) { + throw new Error(`Invalid status: ${value}. Must be one of: ${[...VALID_STATUSES].join(', ')}`) + } + return value +} + +/** Validates a date string is in YYYY-MM-DD format. */ +export function validateDate(value: string, fieldName: string): string { + if (!DATE_REGEX.test(value)) { + throw new Error(`${fieldName} must be in YYYY-MM-DD format, got: ${value}`) + } + return value +} + +/** Validates a date range is a known Google Ads predefined range. */ +export function validateDateRange(value: string): string { + if (!VALID_DATE_RANGES.has(value)) { + throw new Error( + `Invalid date range: ${value}. Must be one of: ${[...VALID_DATE_RANGES].join(', ')}` + ) + } + return value +} + export interface GoogleAdsBaseParams { accessToken: string customerId: string From 129de504c85569863aa727e12a8be301502d4111 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Feb 2026 18:44:34 -0800 Subject: [PATCH 3/5] fix(google-ads): remove deprecated pageSize param, fix searchSettings nesting, add missing date ranges --- apps/docs/content/docs/en/tools/google_ads.mdx | 1 - apps/sim/blocks/blocks/google_ads.ts | 16 +--------------- apps/sim/tools/google_ads/search.ts | 13 +++---------- apps/sim/tools/google_ads/types.ts | 11 ++++++++--- 4 files changed, 12 insertions(+), 29 deletions(-) diff --git a/apps/docs/content/docs/en/tools/google_ads.mdx b/apps/docs/content/docs/en/tools/google_ads.mdx index 8b7baa7d1a..cadc9af6fe 100644 --- a/apps/docs/content/docs/en/tools/google_ads.mdx +++ b/apps/docs/content/docs/en/tools/google_ads.mdx @@ -54,7 +54,6 @@ Run a custom Google Ads Query Language (GAQL) query | `developerToken` | string | Yes | Google Ads API developer token | | `managerCustomerId` | string | No | Manager account customer ID \(if accessing via manager account\) | | `query` | string | Yes | GAQL query to execute | -| `pageSize` | number | No | Maximum number of results per page \(max 10000\) | | `pageToken` | string | No | Page token for pagination | #### Output diff --git a/apps/sim/blocks/blocks/google_ads.ts b/apps/sim/blocks/blocks/google_ads.ts index cb4422a02b..6e4539ad45 100644 --- a/apps/sim/blocks/blocks/google_ads.ts +++ b/apps/sim/blocks/blocks/google_ads.ts @@ -188,15 +188,6 @@ Return ONLY the GAQL query - no explanations, no quotes, no extra text.`, required: { field: 'dateRange', value: 'CUSTOM' }, }, - { - id: 'pageSize', - title: 'Page Size', - type: 'short-input', - placeholder: 'Max results per page (default 10000)', - mode: 'advanced', - condition: { field: 'operation', value: 'search' }, - }, - { id: 'pageToken', title: 'Page Token', @@ -230,7 +221,7 @@ Return ONLY the GAQL query - no explanations, no quotes, no extra text.`, config: { tool: (params) => `google_ads_${params.operation}`, params: (params) => { - const { oauthCredential, dateRange, pageSize, limit, ...rest } = params + const { oauthCredential, dateRange, limit, ...rest } = params const result: Record = { ...rest, @@ -241,10 +232,6 @@ Return ONLY the GAQL query - no explanations, no quotes, no extra text.`, result.dateRange = dateRange } - if (pageSize !== undefined && pageSize !== '') { - result.pageSize = Number(pageSize) - } - if (limit !== undefined && limit !== '') { result.limit = Number(limit) } @@ -266,7 +253,6 @@ Return ONLY the GAQL query - no explanations, no quotes, no extra text.`, dateRange: { type: 'string', description: 'Date range for performance queries' }, startDate: { type: 'string', description: 'Custom start date (YYYY-MM-DD)' }, endDate: { type: 'string', description: 'Custom end date (YYYY-MM-DD)' }, - pageSize: { type: 'number', description: 'Max results per page (max 10000)' }, pageToken: { type: 'string', description: 'Pagination token' }, limit: { type: 'number', description: 'Maximum results to return' }, }, diff --git a/apps/sim/tools/google_ads/search.ts b/apps/sim/tools/google_ads/search.ts index c9e774d64f..0fed9c269c 100644 --- a/apps/sim/tools/google_ads/search.ts +++ b/apps/sim/tools/google_ads/search.ts @@ -44,12 +44,6 @@ export const googleAdsSearchTool: ToolConfig { const body: Record = { query: params.query, - returnTotalResultsCount: true, - } - if (params.pageSize) { - body.pageSize = params.pageSize + searchSettings: { + returnTotalResultsCount: true, + }, } if (params.pageToken) { body.pageToken = params.pageToken diff --git a/apps/sim/tools/google_ads/types.ts b/apps/sim/tools/google_ads/types.ts index 232c93dedf..3f1a9df003 100644 --- a/apps/sim/tools/google_ads/types.ts +++ b/apps/sim/tools/google_ads/types.ts @@ -4,12 +4,18 @@ const NUMERIC_ID_REGEX = /^\d+$/ const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ const VALID_STATUSES = new Set(['ENABLED', 'PAUSED', 'REMOVED']) const VALID_DATE_RANGES = new Set([ + 'TODAY', + 'YESTERDAY', 'LAST_7_DAYS', + 'LAST_14_DAYS', 'LAST_30_DAYS', + 'LAST_BUSINESS_WEEK', 'THIS_MONTH', 'LAST_MONTH', - 'TODAY', - 'YESTERDAY', + 'THIS_WEEK_SUN_TODAY', + 'THIS_WEEK_MON_TODAY', + 'LAST_WEEK_SUN_SAT', + 'LAST_WEEK_MON_SUN', ]) /** Validates that a value is a numeric ID (digits only). */ @@ -61,7 +67,6 @@ export interface GoogleAdsListCustomersParams { export interface GoogleAdsSearchParams extends GoogleAdsBaseParams { query: string - pageSize?: number pageToken?: string } From f40527e8bc357618be321d49135da2d7c24ac2d6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 26 Feb 2026 21:48:16 -0800 Subject: [PATCH 4/5] fix(google-ads): validate managerCustomerId before use in login-customer-id header --- apps/sim/tools/google_ads/ad_performance.ts | 5 ++++- apps/sim/tools/google_ads/campaign_performance.ts | 5 ++++- apps/sim/tools/google_ads/list_ad_groups.ts | 5 ++++- apps/sim/tools/google_ads/list_campaigns.ts | 5 ++++- apps/sim/tools/google_ads/search.ts | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/sim/tools/google_ads/ad_performance.ts b/apps/sim/tools/google_ads/ad_performance.ts index 0595801428..337379298a 100644 --- a/apps/sim/tools/google_ads/ad_performance.ts +++ b/apps/sim/tools/google_ads/ad_performance.ts @@ -96,7 +96,10 @@ export const googleAdsAdPerformanceTool: ToolConfig< 'developer-token': params.developerToken, } if (params.managerCustomerId) { - headers['login-customer-id'] = params.managerCustomerId + headers['login-customer-id'] = validateNumericId( + params.managerCustomerId, + 'managerCustomerId' + ) } return headers }, diff --git a/apps/sim/tools/google_ads/campaign_performance.ts b/apps/sim/tools/google_ads/campaign_performance.ts index cb00cda444..3e4ec688b7 100644 --- a/apps/sim/tools/google_ads/campaign_performance.ts +++ b/apps/sim/tools/google_ads/campaign_performance.ts @@ -84,7 +84,10 @@ export const googleAdsCampaignPerformanceTool: ToolConfig< 'developer-token': params.developerToken, } if (params.managerCustomerId) { - headers['login-customer-id'] = params.managerCustomerId + headers['login-customer-id'] = validateNumericId( + params.managerCustomerId, + 'managerCustomerId' + ) } return headers }, diff --git a/apps/sim/tools/google_ads/list_ad_groups.ts b/apps/sim/tools/google_ads/list_ad_groups.ts index b4606d2e3c..8d8d243031 100644 --- a/apps/sim/tools/google_ads/list_ad_groups.ts +++ b/apps/sim/tools/google_ads/list_ad_groups.ts @@ -77,7 +77,10 @@ export const googleAdsListAdGroupsTool: ToolConfig< 'developer-token': params.developerToken, } if (params.managerCustomerId) { - headers['login-customer-id'] = params.managerCustomerId + headers['login-customer-id'] = validateNumericId( + params.managerCustomerId, + 'managerCustomerId' + ) } return headers }, diff --git a/apps/sim/tools/google_ads/list_campaigns.ts b/apps/sim/tools/google_ads/list_campaigns.ts index fc41d62833..ed738cc955 100644 --- a/apps/sim/tools/google_ads/list_campaigns.ts +++ b/apps/sim/tools/google_ads/list_campaigns.ts @@ -71,7 +71,10 @@ export const googleAdsListCampaignsTool: ToolConfig< 'developer-token': params.developerToken, } if (params.managerCustomerId) { - headers['login-customer-id'] = params.managerCustomerId + headers['login-customer-id'] = validateNumericId( + params.managerCustomerId, + 'managerCustomerId' + ) } return headers }, diff --git a/apps/sim/tools/google_ads/search.ts b/apps/sim/tools/google_ads/search.ts index 0fed9c269c..ffd497fc39 100644 --- a/apps/sim/tools/google_ads/search.ts +++ b/apps/sim/tools/google_ads/search.ts @@ -65,7 +65,10 @@ export const googleAdsSearchTool: ToolConfig Date: Thu, 26 Feb 2026 21:54:54 -0800 Subject: [PATCH 5/5] chore(docs): regenerate docs after google ads integration --- apps/docs/components/ui/icon-mapping.ts | 12 ++++++------ apps/docs/content/docs/en/tools/meta.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 3b26a2c523..c410ab33d8 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -39,8 +39,8 @@ import { EyeIcon, FirecrawlIcon, FirefliesIcon, - GithubIcon, GitLabIcon, + GithubIcon, GmailIcon, GongIcon, GoogleAdsIcon, @@ -77,9 +77,9 @@ import { LinearIcon, LinkedInIcon, LinkupIcon, + MailServerIcon, MailchimpIcon, MailgunIcon, - MailServerIcon, Mem0Icon, MicrosoftDataverseIcon, MicrosoftExcelIcon, @@ -112,6 +112,8 @@ import { ResendIcon, RevenueCatIcon, S3Icon, + SQSIcon, + STTIcon, SalesforceIcon, SearchIcon, SendgridIcon, @@ -123,19 +125,17 @@ import { SimilarwebIcon, SlackIcon, SmtpIcon, - SQSIcon, SshIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TextractIcon, TinybirdIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, UpstashIcon, @@ -146,11 +146,11 @@ import { WhatsAppIcon, WikipediaIcon, WordpressIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, ZoomIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 4da47afb4d..c7d262ee9c 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -151,4 +151,4 @@ "zep", "zoom" ] -} +} \ No newline at end of file