fix(amplitude): correct wire formats and add funnels/retention analytics (#5355)

* fix(amplitude): validate integration against API docs, add funnels/retention

- Fix Identify/Group Identify sending JSON instead of the form-urlencoded body Amplitude requires
- Fix Send Event using snake_case product_id/revenue_type instead of Amplitude's camelCase productId/revenueType
- Fix Get Revenue parsing a response shape that never matched the real Revenue LTV API
- Add EU data residency support across all tools
- Add missing filters/formula/segment params to Event Segmentation, groupBy/segment to Active Users and Revenue
- Add first_used/last_used to User Activity output, non_active/flow_hidden to List Events output
- Add real-time/hourly interval options to Event Segmentation
- Add Funnels and Retention tools for conversion and retention analysis
- Harden JSON-shape validation across funnels/segmentation/retention (fail loudly on malformed or partial input instead of silently degrading)
- Expose every tool output field (user_profile, send_event, user_search) on the block so nothing is unreachable downstream
- Update brand colors to current Amplitude guide

* fix(amplitude): validate retention brackets, require formula param, add segmentation 2nd group-by

- Retention now validates retentionBrackets as a JSON array and requires it when retentionMode is "bracket", matching the block UI's requirement
- Event Segmentation now throws if metric is "formula" but no formula is provided, matching the block UI's requirement
- Event Segmentation now supports a documented second group-by property via groupBy2/g2
- Corrected Get Active Users' group-by copy — Amplitude's docs don't document a second-property syntax for /api/2/users the way they do for segmentation's g2, so the field no longer overpromises "max two"
This commit is contained in:
Waleed
2026-07-02 10:25:59 -07:00
committed by GitHub
parent 59d6b8a62e
commit 0507acf2dd
18 changed files with 1150 additions and 45 deletions
+437 -6
View File
@@ -6,12 +6,12 @@ export const AmplitudeBlock: BlockConfig = {
name: 'Amplitude',
description: 'Track events and query analytics from Amplitude',
longDescription:
'Integrate Amplitude into your workflow to track events, identify users and groups, search for users, query analytics, and retrieve revenue data.',
'Integrate Amplitude into your workflow to track events, identify users and groups, search for users, query analytics, analyze funnels and retention, and retrieve revenue data.',
docsLink: 'https://docs.sim.ai/integrations/amplitude',
category: 'tools',
integrationType: IntegrationType.Analytics,
bgColor: '#1B1F3B',
iconColor: '#1F77E0',
bgColor: '#13294B',
iconColor: '#1E61F0',
icon: AmplitudeIcon,
authMode: AuthMode.ApiKey,
@@ -32,6 +32,8 @@ export const AmplitudeBlock: BlockConfig = {
{ label: 'Real-time Active Users', id: 'realtime_active_users' },
{ label: 'List Events', id: 'list_events' },
{ label: 'Get Revenue', id: 'get_revenue' },
{ label: 'Funnels', id: 'funnels' },
{ label: 'Retention', id: 'retention' },
],
value: () => 'send_event',
},
@@ -70,6 +72,8 @@ export const AmplitudeBlock: BlockConfig = {
'realtime_active_users',
'list_events',
'get_revenue',
'funnels',
'retention',
],
},
placeholder: 'Enter your Amplitude Secret Key',
@@ -85,10 +89,26 @@ export const AmplitudeBlock: BlockConfig = {
'realtime_active_users',
'list_events',
'get_revenue',
'funnels',
'retention',
],
},
},
// Data Residency (all operations except User Profile, which is US-only)
{
id: 'dataResidency',
title: 'Data Residency',
type: 'dropdown',
options: [
{ label: 'US (default)', id: 'us' },
{ label: 'EU', id: 'eu' },
],
value: () => 'us',
condition: { field: 'operation', value: 'user_profile', not: true },
mode: 'advanced',
},
// --- Send Event fields ---
{
id: 'eventType',
@@ -460,6 +480,8 @@ export const AmplitudeBlock: BlockConfig = {
title: 'Interval',
type: 'dropdown',
options: [
{ label: 'Real-time', id: '-300000' },
{ label: 'Hourly', id: '-3600000' },
{ label: 'Daily', id: '1' },
{ label: 'Weekly', id: '7' },
{ label: 'Monthly', id: '30' },
@@ -476,6 +498,14 @@ export const AmplitudeBlock: BlockConfig = {
condition: { field: 'operation', value: 'event_segmentation' },
mode: 'advanced',
},
{
id: 'segmentationGroupBy2',
title: 'Group By (2nd Property)',
type: 'short-input',
placeholder: 'Second property name (prefix custom with "gp:")',
condition: { field: 'operation', value: 'event_segmentation' },
mode: 'advanced',
},
{
id: 'segmentationLimit',
title: 'Limit',
@@ -484,6 +514,46 @@ export const AmplitudeBlock: BlockConfig = {
condition: { field: 'operation', value: 'event_segmentation' },
mode: 'advanced',
},
{
id: 'segmentationFilters',
title: 'Filters',
type: 'long-input',
placeholder:
'[{"subprop_type":"event","subprop_key":"city","subprop_op":"is","subprop_value":["San Francisco"]}]',
condition: { field: 'operation', value: 'event_segmentation' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of Amplitude event segmentation filter objects, each with subprop_type ("event" or "user"), subprop_key, subprop_op (e.g. "is", "is not", "contains"), and subprop_value (array of strings). Return ONLY the JSON array - no explanations, no extra text.',
generationType: 'json-object',
},
},
{
id: 'segmentationFormula',
title: 'Formula',
type: 'short-input',
placeholder: 'e.g., UNIQUES(A)/UNIQUES(B) — required when Metric is Formula',
condition: {
field: 'operation',
value: 'event_segmentation',
and: { field: 'segmentationMetric', value: 'formula' },
},
required: {
field: 'operation',
value: 'event_segmentation',
and: { field: 'segmentationMetric', value: 'formula' },
},
mode: 'advanced',
},
{
id: 'segmentationSegment',
title: 'Segment Definition',
type: 'long-input',
placeholder: 'JSON segment definition(s)',
condition: { field: 'operation', value: 'event_segmentation' },
mode: 'advanced',
},
// --- Get Active Users fields ---
{
@@ -539,6 +609,22 @@ export const AmplitudeBlock: BlockConfig = {
condition: { field: 'operation', value: 'get_active_users' },
mode: 'advanced',
},
{
id: 'activeUsersGroupBy',
title: 'Group By',
type: 'short-input',
placeholder: 'Property name',
condition: { field: 'operation', value: 'get_active_users' },
mode: 'advanced',
},
{
id: 'activeUsersSegment',
title: 'Segment Definition',
type: 'long-input',
placeholder: 'JSON segment definition(s)',
condition: { field: 'operation', value: 'get_active_users' },
mode: 'advanced',
},
// --- Get Revenue fields ---
{
@@ -596,6 +682,243 @@ export const AmplitudeBlock: BlockConfig = {
condition: { field: 'operation', value: 'get_revenue' },
mode: 'advanced',
},
{
id: 'revenueGroupBy',
title: 'Group By',
type: 'short-input',
placeholder: 'Property name (limit: one)',
condition: { field: 'operation', value: 'get_revenue' },
mode: 'advanced',
},
{
id: 'revenueSegment',
title: 'Segment Definition',
type: 'long-input',
placeholder: 'JSON segment definition(s)',
condition: { field: 'operation', value: 'get_revenue' },
mode: 'advanced',
},
// --- Funnels fields ---
{
id: 'funnelEvents',
title: 'Funnel Steps',
type: 'long-input',
required: { field: 'operation', value: 'funnels' },
placeholder: '[{"event_type":"signup"},{"event_type":"purchase"}]',
condition: { field: 'operation', value: 'funnels' },
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of Amplitude event objects, one per funnel step in order, each with an "event_type" key. Return ONLY the JSON array - no explanations, no extra text.',
generationType: 'json-object',
},
},
{
id: 'funnelStart',
title: 'Start Date',
type: 'short-input',
required: { field: 'operation', value: 'funnels' },
placeholder: 'YYYYMMDD',
condition: { field: 'operation', value: 'funnels' },
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYYMMDD format. Return ONLY the date string - no explanations, no extra text.',
generationType: 'timestamp',
},
},
{
id: 'funnelEnd',
title: 'End Date',
type: 'short-input',
required: { field: 'operation', value: 'funnels' },
placeholder: 'YYYYMMDD',
condition: { field: 'operation', value: 'funnels' },
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYYMMDD format. Return ONLY the date string - no explanations, no extra text.',
generationType: 'timestamp',
},
},
{
id: 'funnelMode',
title: 'Funnel Mode',
type: 'dropdown',
options: [
{ label: 'Ordered', id: 'ordered' },
{ label: 'Unordered', id: 'unordered' },
{ label: 'Sequential', id: 'sequential' },
],
value: () => 'ordered',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelUserType',
title: 'User Type',
type: 'dropdown',
options: [
{ label: 'Active', id: 'active' },
{ label: 'New', id: 'new' },
],
value: () => 'active',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelInterval',
title: 'Interval',
type: 'dropdown',
options: [
{ label: 'Real-time', id: '-300000' },
{ label: 'Hourly', id: '-3600000' },
{ label: 'Daily', id: '1' },
{ label: 'Weekly', id: '7' },
{ label: 'Monthly', id: '30' },
],
value: () => '1',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelConversionWindowSeconds',
title: 'Conversion Window (seconds)',
type: 'short-input',
placeholder: '2592000 (30 days)',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelGroupBy',
title: 'Group By',
type: 'short-input',
placeholder: 'Property name (limit: one)',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelLimit',
title: 'Limit',
type: 'short-input',
placeholder: 'Max group-by values (max 1000)',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
{
id: 'funnelSegment',
title: 'Segment Definition',
type: 'long-input',
placeholder: 'JSON segment definition(s)',
condition: { field: 'operation', value: 'funnels' },
mode: 'advanced',
},
// --- Retention fields ---
{
id: 'retentionStartEvent',
title: 'Starting Event',
type: 'short-input',
required: { field: 'operation', value: 'retention' },
placeholder: '{"event_type":"_new"}',
condition: { field: 'operation', value: 'retention' },
},
{
id: 'retentionReturnEvent',
title: 'Returning Event',
type: 'short-input',
required: { field: 'operation', value: 'retention' },
placeholder: '{"event_type":"_all"}',
condition: { field: 'operation', value: 'retention' },
},
{
id: 'retentionStart',
title: 'Start Date',
type: 'short-input',
required: { field: 'operation', value: 'retention' },
placeholder: 'YYYYMMDD',
condition: { field: 'operation', value: 'retention' },
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYYMMDD format. Return ONLY the date string - no explanations, no extra text.',
generationType: 'timestamp',
},
},
{
id: 'retentionEnd',
title: 'End Date',
type: 'short-input',
required: { field: 'operation', value: 'retention' },
placeholder: 'YYYYMMDD',
condition: { field: 'operation', value: 'retention' },
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYYMMDD format. Return ONLY the date string - no explanations, no extra text.',
generationType: 'timestamp',
},
},
{
id: 'retentionMode',
title: 'Retention Mode',
type: 'dropdown',
options: [
{ label: 'N-Day', id: 'n-day' },
{ label: 'Rolling', id: 'rolling' },
{ label: 'Bracket', id: 'bracket' },
],
value: () => 'n-day',
condition: { field: 'operation', value: 'retention' },
mode: 'advanced',
},
{
id: 'retentionBrackets',
title: 'Retention Brackets',
type: 'short-input',
placeholder: '[[0,4]] — required when Retention Mode is Bracket',
condition: {
field: 'operation',
value: 'retention',
and: { field: 'retentionMode', value: 'bracket' },
},
required: {
field: 'operation',
value: 'retention',
and: { field: 'retentionMode', value: 'bracket' },
},
mode: 'advanced',
},
{
id: 'retentionInterval',
title: 'Interval',
type: 'dropdown',
options: [
{ label: 'Daily', id: '1' },
{ label: 'Weekly', id: '7' },
{ label: 'Monthly', id: '30' },
],
value: () => '1',
condition: { field: 'operation', value: 'retention' },
mode: 'advanced',
},
{
id: 'retentionGroupBy',
title: 'Group By',
type: 'short-input',
placeholder: 'Property name (limit: one)',
condition: { field: 'operation', value: 'retention' },
mode: 'advanced',
},
{
id: 'retentionSegment',
title: 'Segment Definition',
type: 'long-input',
placeholder: 'JSON segment definition(s)',
condition: { field: 'operation', value: 'retention' },
mode: 'advanced',
},
],
tools: {
@@ -611,6 +934,8 @@ export const AmplitudeBlock: BlockConfig = {
'amplitude_realtime_active_users',
'amplitude_list_events',
'amplitude_get_revenue',
'amplitude_funnels',
'amplitude_retention',
],
config: {
tool: (params) => `amplitude_${params.operation}`,
@@ -649,7 +974,11 @@ export const AmplitudeBlock: BlockConfig = {
if (params.segmentationMetric) result.metric = params.segmentationMetric
if (params.segmentationInterval) result.interval = params.segmentationInterval
if (params.segmentationGroupBy) result.groupBy = params.segmentationGroupBy
if (params.segmentationGroupBy2) result.groupBy2 = params.segmentationGroupBy2
if (params.segmentationLimit) result.limit = params.segmentationLimit
if (params.segmentationFilters) result.filters = params.segmentationFilters
if (params.segmentationFormula) result.formula = params.segmentationFormula
if (params.segmentationSegment) result.segment = params.segmentationSegment
break
case 'get_active_users':
@@ -657,6 +986,8 @@ export const AmplitudeBlock: BlockConfig = {
if (params.activeUsersEnd) result.end = params.activeUsersEnd
if (params.activeUsersMetric) result.metric = params.activeUsersMetric
if (params.activeUsersInterval) result.interval = params.activeUsersInterval
if (params.activeUsersGroupBy) result.groupBy = params.activeUsersGroupBy
if (params.activeUsersSegment) result.segment = params.activeUsersSegment
break
case 'get_revenue':
@@ -664,6 +995,34 @@ export const AmplitudeBlock: BlockConfig = {
if (params.revenueEnd) result.end = params.revenueEnd
if (params.revenueMetric) result.metric = params.revenueMetric
if (params.revenueInterval) result.interval = params.revenueInterval
if (params.revenueGroupBy) result.groupBy = params.revenueGroupBy
if (params.revenueSegment) result.segment = params.revenueSegment
break
case 'funnels':
if (params.funnelEvents) result.events = params.funnelEvents
if (params.funnelStart) result.start = params.funnelStart
if (params.funnelEnd) result.end = params.funnelEnd
if (params.funnelMode) result.mode = params.funnelMode
if (params.funnelUserType) result.userType = params.funnelUserType
if (params.funnelInterval) result.interval = params.funnelInterval
if (params.funnelConversionWindowSeconds)
result.conversionWindowSeconds = params.funnelConversionWindowSeconds
if (params.funnelGroupBy) result.groupBy = params.funnelGroupBy
if (params.funnelLimit) result.limit = params.funnelLimit
if (params.funnelSegment) result.segment = params.funnelSegment
break
case 'retention':
if (params.retentionStartEvent) result.startEvent = params.retentionStartEvent
if (params.retentionReturnEvent) result.returnEvent = params.retentionReturnEvent
if (params.retentionStart) result.start = params.retentionStart
if (params.retentionEnd) result.end = params.retentionEnd
if (params.retentionMode) result.retentionMode = params.retentionMode
if (params.retentionBrackets) result.retentionBrackets = params.retentionBrackets
if (params.retentionInterval) result.interval = params.retentionInterval
if (params.retentionGroupBy) result.groupBy = params.retentionGroupBy
if (params.retentionSegment) result.segment = params.retentionSegment
break
}
@@ -696,6 +1055,32 @@ export const AmplitudeBlock: BlockConfig = {
activeUsersEnd: { type: 'string', description: 'Active users end date' },
revenueStart: { type: 'string', description: 'Revenue start date' },
revenueEnd: { type: 'string', description: 'Revenue end date' },
dataResidency: { type: 'string', description: 'Data residency region: "us" or "eu"' },
segmentationFilters: { type: 'string', description: 'Event segmentation filters JSON' },
segmentationFormula: { type: 'string', description: 'Event segmentation formula expression' },
segmentationGroupBy2: {
type: 'string',
description: 'Event segmentation second group-by property',
},
segmentationSegment: {
type: 'string',
description: 'Event segmentation segment definition JSON',
},
activeUsersGroupBy: { type: 'string', description: 'Active users group-by property' },
activeUsersSegment: { type: 'string', description: 'Active users segment definition JSON' },
revenueGroupBy: { type: 'string', description: 'Revenue group-by property' },
revenueSegment: { type: 'string', description: 'Revenue segment definition JSON' },
funnelEvents: { type: 'string', description: 'Funnel step event objects JSON array' },
funnelStart: { type: 'string', description: 'Funnel analysis start date' },
funnelEnd: { type: 'string', description: 'Funnel analysis end date' },
funnelGroupBy: { type: 'string', description: 'Funnel group-by property' },
funnelSegment: { type: 'string', description: 'Funnel segment definition JSON' },
retentionStartEvent: { type: 'string', description: 'Retention starting event JSON object' },
retentionReturnEvent: { type: 'string', description: 'Retention returning event JSON object' },
retentionStart: { type: 'string', description: 'Retention analysis start date' },
retentionEnd: { type: 'string', description: 'Retention analysis end date' },
retentionGroupBy: { type: 'string', description: 'Retention group-by property' },
retentionSegment: { type: 'string', description: 'Retention segment definition JSON' },
},
outputs: {
@@ -711,10 +1096,43 @@ export const AmplitudeBlock: BlockConfig = {
type: 'number',
description: 'Number of events ingested (send_event)',
},
payloadSizeBytes: {
type: 'number',
description: 'Size of the ingested payload in bytes (send_event)',
},
serverUploadTime: {
type: 'number',
description: 'Server-side upload timestamp (send_event)',
},
matches: {
type: 'json',
description: 'User search matches (amplitudeId, userId)',
},
type: {
type: 'string',
description: 'Match type, e.g. match_user_or_device_id (user_search)',
},
userId: {
type: 'string',
description: 'External user ID (user_profile)',
},
deviceId: {
type: 'string',
description: 'Device ID (user_profile)',
},
ampProps: {
type: 'json',
description:
'Amplitude user properties (library, first_used, last_used, custom) (user_profile)',
},
cohortIds: {
type: 'json',
description: 'Cohort IDs the user belongs to (user_profile)',
},
computations: {
type: 'json',
description: 'Computed user properties (user_profile)',
},
events: {
type: 'json',
description: 'Event list (list_events, user_activity)',
@@ -725,7 +1143,8 @@ export const AmplitudeBlock: BlockConfig = {
},
series: {
type: 'json',
description: 'Time-series data (segmentation, active_users, revenue, realtime)',
description:
'Time-series data (segmentation, active_users, realtime: number[][]; revenue: [{dates, values}]; retention: [{dates, values, combined}])',
},
seriesLabels: {
type: 'json',
@@ -733,7 +1152,7 @@ export const AmplitudeBlock: BlockConfig = {
},
seriesMeta: {
type: 'json',
description: 'Metadata labels for data series (active_users)',
description: 'Metadata labels for data series (active_users, retention)',
},
seriesCollapsed: {
type: 'json',
@@ -741,7 +1160,12 @@ export const AmplitudeBlock: BlockConfig = {
},
xValues: {
type: 'json',
description: 'X-axis date/time values for chart data',
description: 'X-axis date/time values for chart data (segmentation, active_users, realtime)',
},
funnels: {
type: 'json',
description:
'Funnel results per segment (stepByStep, cumulative, medianTransTimes, dayFunnels, etc.) (funnels)',
},
},
}
@@ -849,5 +1273,12 @@ export const AmplitudeBlockMeta = {
content:
'# Lookup User Activity\n\nInvestigate a single user in Amplitude for support or debugging.\n\n## Steps\n1. Search for the user by user ID, device ID, or Amplitude ID to resolve their Amplitude ID.\n2. Pull the user activity stream for that Amplitude ID, ordered latest first.\n3. Optionally fetch the user profile to see their current properties.\n\n## Output\nA timeline of the user recent events plus key profile properties. Note the time range covered.',
},
{
name: 'analyze-conversion-funnel',
description:
'Run an Amplitude funnel across a sequence of events to find conversion rates and drop-off points.',
content:
'# Analyze Conversion Funnel\n\nMeasure how users progress through a multi-step flow in Amplitude.\n\n## Steps\n1. Define the ordered sequence of events that make up the funnel (e.g., signup, activation, purchase).\n2. Pick the date range and, if useful, a property to group by.\n3. Run the funnel query and read the step-by-step and cumulative conversion numbers.\n\n## Output\nConversion counts and rates at each step, the biggest drop-off point, and any group-by breakdown.',
},
],
} as const satisfies BlockMeta
+60 -3
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeEventSegmentationParams,
AmplitudeEventSegmentationResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const eventSegmentationTool: ToolConfig<
@@ -64,25 +65,81 @@ export const eventSegmentationTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Property name to group by (prefix custom user properties with "gp:")',
},
groupBy2: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Second property name to group by (prefix custom user properties with "gp:")',
},
limit: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of group-by values (max 1000)',
},
filters: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array of filter objects applied to the event, e.g. [{"subprop_type":"event","subprop_key":"city","subprop_op":"is","subprop_value":["San Francisco"]}]',
},
formula: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required when metric is "formula", e.g. "UNIQUES(A)/UNIQUES(B)"',
},
segment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON segment definition(s) applied to the query',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL('https://amplitude.com/api/2/events/segmentation')
const eventObj = JSON.stringify({ event_type: params.eventType })
url.searchParams.set('e', eventObj)
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/events/segmentation`)
const event: Record<string, unknown> = { event_type: params.eventType }
if (params.filters) {
let parsedFilters: unknown
try {
parsedFilters = JSON.parse(params.filters)
} catch {
parsedFilters = undefined
}
if (!Array.isArray(parsedFilters)) {
throw new Error(
'Amplitude Event Segmentation: "filters" must be a valid JSON array of filter objects'
)
}
event.filters = parsedFilters
}
if (params.metric === 'formula' && !params.formula) {
throw new Error(
'Amplitude Event Segmentation: "formula" is required when metric is "formula"'
)
}
url.searchParams.set('e', JSON.stringify(event))
url.searchParams.set('start', params.start)
url.searchParams.set('end', params.end)
if (params.metric) url.searchParams.set('m', params.metric)
if (params.interval) url.searchParams.set('i', params.interval)
if (params.groupBy) url.searchParams.set('g', params.groupBy)
if (params.groupBy2) url.searchParams.set('g2', params.groupBy2)
if (params.limit) url.searchParams.set('limit', params.limit)
if (params.formula) url.searchParams.set('formula', params.formula)
if (params.segment) url.searchParams.set('s', params.segment)
return url.toString()
},
method: 'GET',
+195
View File
@@ -0,0 +1,195 @@
import type { AmplitudeFunnelsParams, AmplitudeFunnelsResponse } from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const funnelsTool: ToolConfig<AmplitudeFunnelsParams, AmplitudeFunnelsResponse> = {
id: 'amplitude_funnels',
name: 'Amplitude Funnels',
description: 'Analyze conversion rates and drop-off between a sequence of events.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Amplitude API Key',
},
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Amplitude Secret Key',
},
events: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of event objects, one per funnel step in order, e.g. [{"event_type":"signup"},{"event_type":"purchase"}]',
},
start: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date in YYYYMMDD format',
},
end: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date in YYYYMMDD format',
},
mode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Funnel ordering: "ordered", "unordered", or "sequential" (default: ordered)',
},
userType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User type: "new" or "active" (default: active)',
},
interval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Time interval: -300000 (real-time), -3600000 (hourly), 1 (daily), 7 (weekly), or 30 (monthly)',
},
conversionWindowSeconds: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Conversion window in seconds (default: 2592000, i.e. 30 days)',
},
groupBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Property to group by (limit: one; prefix custom properties with "gp:")',
},
limit: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of group-by values (default: 100, max: 1000)',
},
segment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON segment definition(s) applied to the query',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/funnels`)
let parsed: unknown
try {
parsed = JSON.parse(params.events)
} catch {
throw new Error('Amplitude Funnels: "events" must be a valid JSON array of event objects')
}
const isPlainObject = (value: unknown): value is Record<string, unknown> =>
Boolean(value) && typeof value === 'object' && !Array.isArray(value)
if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isPlainObject)) {
throw new Error(
'Amplitude Funnels: "events" must be a non-empty JSON array of event objects'
)
}
for (const step of parsed) {
url.searchParams.append('e', JSON.stringify(step))
}
url.searchParams.set('start', params.start)
url.searchParams.set('end', params.end)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.userType) url.searchParams.set('n', params.userType)
if (params.interval) url.searchParams.set('i', params.interval)
if (params.conversionWindowSeconds) url.searchParams.set('cs', params.conversionWindowSeconds)
if (params.groupBy) url.searchParams.set('g', params.groupBy)
if (params.limit) url.searchParams.set('limit', params.limit)
if (params.segment) url.searchParams.set('s', params.segment)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:${params.secretKey}`)}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || `Amplitude Funnels API error: ${response.status}`)
}
const results = (Array.isArray(data.data) ? data.data : []) as Array<Record<string, unknown>>
const funnels = results.map((r) => {
const dayFunnels = r.dayFunnels as Record<string, unknown> | undefined
return {
stepByStep: (r.stepByStep as number[]) ?? [],
cumulative: (r.cumulative as number[]) ?? [],
cumulativeRaw: (r.cumulativeRaw as number[]) ?? [],
medianTransTimes: (r.medianTransTimes as number[]) ?? [],
avgTransTimes: (r.avgTransTimes as number[]) ?? [],
events: (r.events as string[]) ?? [],
dayFunnels: dayFunnels
? {
series: (dayFunnels.series as number[][]) ?? [],
xValues: (dayFunnels.xValues as string[]) ?? [],
}
: null,
}
})
return {
success: true,
output: { funnels },
}
},
outputs: {
funnels: {
type: 'array',
description: 'Funnel results, one entry per segment',
items: {
type: 'object',
properties: {
stepByStep: { type: 'json', description: 'Conversion count at each step' },
cumulative: {
type: 'json',
description: 'Cumulative conversion percentage at each step',
},
cumulativeRaw: { type: 'json', description: 'Cumulative conversion count at each step' },
medianTransTimes: {
type: 'json',
description: 'Median transition time between steps (ms)',
},
avgTransTimes: {
type: 'json',
description: 'Average transition time between steps (ms)',
},
events: { type: 'json', description: 'Event names for each funnel step' },
dayFunnels: {
type: 'json',
description: 'Daily funnel breakdown {series, xValues}',
optional: true,
},
},
},
},
},
}
+22 -1
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeGetActiveUsersParams,
AmplitudeGetActiveUsersResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const getActiveUsersTool: ToolConfig<
@@ -50,15 +51,35 @@ export const getActiveUsersTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Time interval: 1 (daily), 7 (weekly), or 30 (monthly)',
},
groupBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Property name to group by',
},
segment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON segment definition(s) applied to the query',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL('https://amplitude.com/api/2/users')
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/users`)
url.searchParams.set('start', params.start)
url.searchParams.set('end', params.end)
if (params.metric) url.searchParams.set('m', params.metric)
if (params.interval) url.searchParams.set('i', params.interval)
if (params.groupBy) url.searchParams.set('g', params.groupBy)
if (params.segment) url.searchParams.set('s', params.segment)
return url.toString()
},
method: 'GET',
+40 -9
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeGetRevenueParams,
AmplitudeGetRevenueResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const getRevenueTool: ToolConfig<AmplitudeGetRevenueParams, AmplitudeGetRevenueResponse> = {
@@ -47,15 +48,35 @@ export const getRevenueTool: ToolConfig<AmplitudeGetRevenueParams, AmplitudeGetR
visibility: 'user-or-llm',
description: 'Time interval: 1 (daily), 7 (weekly), or 30 (monthly)',
},
groupBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Property name to group by (limit: one)',
},
segment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON segment definition(s) applied to the query',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL('https://amplitude.com/api/2/revenue/ltv')
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/revenue/ltv`)
url.searchParams.set('start', params.start)
url.searchParams.set('end', params.end)
if (params.metric) url.searchParams.set('m', params.metric)
if (params.interval) url.searchParams.set('i', params.interval)
if (params.groupBy) url.searchParams.set('g', params.groupBy)
if (params.segment) url.searchParams.set('s', params.segment)
return url.toString()
},
method: 'GET',
@@ -78,25 +99,35 @@ export const getRevenueTool: ToolConfig<AmplitudeGetRevenueParams, AmplitudeGetR
output: {
series: result.series ?? [],
seriesLabels: result.seriesLabels ?? [],
xValues: result.xValues ?? [],
},
}
},
outputs: {
series: {
type: 'json',
description: 'Array of revenue data series',
type: 'array',
description:
'Revenue data series [{dates: [YYYY-MM-DD], values: {<date>: {r1d..r90d, count, paid, total_amount}}}]',
items: {
type: 'json',
properties: {
dates: {
type: 'array',
description: 'Dates covered by this series',
items: { type: 'string' },
},
values: {
type: 'json',
description:
'Per-date metric values keyed by date (r1d..r90d, count, paid, total_amount)',
},
},
},
},
seriesLabels: {
type: 'array',
description: 'Labels for each data series',
items: { type: 'string' },
},
xValues: {
type: 'array',
description: 'Date values for the x-axis',
items: { type: 'string' },
},
},
}
+22 -11
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeGroupIdentifyParams,
AmplitudeGroupIdentifyResponse,
} from '@/tools/amplitude/types'
import { getIngestionHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const groupIdentifyTool: ToolConfig<
@@ -40,13 +41,19 @@ export const groupIdentifyTool: ToolConfig<
description:
'JSON object of group properties. Use operations like $set, $setOnce, $add, $append, $unset.',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: 'https://api2.amplitude.com/groupidentify',
url: (params) => `${getIngestionHost(params.dataResidency)}/groupidentify`,
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}),
body: (params) => {
let groupProperties: Record<string, unknown> = {}
@@ -56,16 +63,20 @@ export const groupIdentifyTool: ToolConfig<
groupProperties = {}
}
return {
const identification = [
{
group_type: params.groupType,
group_value: params.groupValue,
group_properties: groupProperties,
},
]
const body = new URLSearchParams({
api_key: params.apiKey,
identification: [
{
group_type: params.groupType,
group_value: params.groupValue,
group_properties: groupProperties,
},
],
}
identification: JSON.stringify(identification),
})
return body.toString()
},
},
+14 -5
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeIdentifyUserParams,
AmplitudeIdentifyUserResponse,
} from '@/tools/amplitude/types'
import { getIngestionHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const identifyUserTool: ToolConfig<
@@ -40,13 +41,19 @@ export const identifyUserTool: ToolConfig<
description:
'JSON object of user properties. Use operations like $set, $setOnce, $add, $append, $unset.',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: 'https://api2.amplitude.com/identify',
url: (params) => `${getIngestionHost(params.dataResidency)}/identify`,
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}),
body: (params) => {
const identification: Record<string, unknown> = {}
@@ -60,10 +67,12 @@ export const identifyUserTool: ToolConfig<
identification.user_properties = {}
}
return {
const body = new URLSearchParams({
api_key: params.apiKey,
identification: [identification],
}
identification: JSON.stringify([identification]),
})
return body.toString()
},
},
+4
View File
@@ -1,10 +1,12 @@
import { eventSegmentationTool } from '@/tools/amplitude/event_segmentation'
import { funnelsTool } from '@/tools/amplitude/funnels'
import { getActiveUsersTool } from '@/tools/amplitude/get_active_users'
import { getRevenueTool } from '@/tools/amplitude/get_revenue'
import { groupIdentifyTool } from '@/tools/amplitude/group_identify'
import { identifyUserTool } from '@/tools/amplitude/identify_user'
import { listEventsTool } from '@/tools/amplitude/list_events'
import { realtimeActiveUsersTool } from '@/tools/amplitude/realtime_active_users'
import { retentionTool } from '@/tools/amplitude/retention'
import { sendEventTool } from '@/tools/amplitude/send_event'
import { userActivityTool } from '@/tools/amplitude/user_activity'
import { userProfileTool } from '@/tools/amplitude/user_profile'
@@ -21,3 +23,5 @@ export const amplitudeGetActiveUsersTool = getActiveUsersTool
export const amplitudeRealtimeActiveUsersTool = realtimeActiveUsersTool
export const amplitudeListEventsTool = listEventsTool
export const amplitudeGetRevenueTool = getRevenueTool
export const amplitudeFunnelsTool = funnelsTool
export const amplitudeRetentionTool = retentionTool
+18 -1
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeListEventsParams,
AmplitudeListEventsResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const listEventsTool: ToolConfig<AmplitudeListEventsParams, AmplitudeListEventsResponse> = {
@@ -24,10 +25,16 @@ export const listEventsTool: ToolConfig<AmplitudeListEventsParams, AmplitudeList
visibility: 'user-only',
description: 'Amplitude Secret Key',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: 'https://amplitude.com/api/2/events/list',
url: (params) => `${getDashboardHost(params.dataResidency)}/api/2/events/list`,
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:${params.secretKey}`)}`,
@@ -49,6 +56,8 @@ export const listEventsTool: ToolConfig<AmplitudeListEventsParams, AmplitudeList
totals: (e.totals as number) ?? 0,
hidden: (e.hidden as boolean) ?? false,
deleted: (e.deleted as boolean) ?? false,
nonActive: (e.non_active as boolean) ?? false,
flowHidden: (e.flow_hidden as boolean) ?? false,
}) as const
)
@@ -72,6 +81,14 @@ export const listEventsTool: ToolConfig<AmplitudeListEventsParams, AmplitudeList
totals: { type: 'number', description: 'Weekly total count' },
hidden: { type: 'boolean', description: 'Whether the event is hidden' },
deleted: { type: 'boolean', description: 'Whether the event is deleted' },
nonActive: {
type: 'boolean',
description: 'Whether the event is excluded from active user calculations',
},
flowHidden: {
type: 'boolean',
description: 'Whether the event is hidden from user flow charts',
},
},
},
},
@@ -2,6 +2,7 @@ import type {
AmplitudeRealtimeActiveUsersParams,
AmplitudeRealtimeActiveUsersResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const realtimeActiveUsersTool: ToolConfig<
@@ -26,10 +27,16 @@ export const realtimeActiveUsersTool: ToolConfig<
visibility: 'user-only',
description: 'Amplitude Secret Key',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: 'https://amplitude.com/api/2/realtime',
url: (params) => `${getDashboardHost(params.dataResidency)}/api/2/realtime`,
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:${params.secretKey}`)}`,
+186
View File
@@ -0,0 +1,186 @@
import type { AmplitudeRetentionParams, AmplitudeRetentionResponse } from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const retentionTool: ToolConfig<AmplitudeRetentionParams, AmplitudeRetentionResponse> = {
id: 'amplitude_retention',
name: 'Amplitude Retention',
description: 'Measure how many users return to perform an action after a starting action.',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Amplitude API Key',
},
secretKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Amplitude Secret Key',
},
startEvent: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON starting event object, e.g. {"event_type":"_new"} or {"event_type":"_active"}',
},
returnEvent: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON returning event object, e.g. {"event_type":"_all"} or {"event_type":"_active"}',
},
start: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start date in YYYYMMDD format',
},
end: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End date in YYYYMMDD format',
},
retentionMode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Retention type: "bracket", "rolling", or "n-day" (default: n-day)',
},
retentionBrackets: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Required when Retention Mode is "bracket". Day ranges, e.g. [[0,4]]',
},
interval: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Time interval: 1 (daily), 7 (weekly), or 30 (monthly)',
},
groupBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Property to group by (limit: one; prefix custom properties with "gp:")',
},
segment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON segment definition(s) applied to the query',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/retention`)
const parseEventObject = (value: string, fieldName: string): Record<string, unknown> => {
let parsed: unknown
try {
parsed = JSON.parse(value)
} catch {
parsed = undefined
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`Amplitude Retention: "${fieldName}" must be a valid JSON event object`)
}
return parsed as Record<string, unknown>
}
url.searchParams.set('se', JSON.stringify(parseEventObject(params.startEvent, 'startEvent')))
url.searchParams.set(
're',
JSON.stringify(parseEventObject(params.returnEvent, 'returnEvent'))
)
url.searchParams.set('start', params.start)
url.searchParams.set('end', params.end)
if (params.retentionMode) url.searchParams.set('rm', params.retentionMode)
if (params.retentionMode === 'bracket') {
if (!params.retentionBrackets) {
throw new Error(
'Amplitude Retention: "retentionBrackets" is required when Retention Mode is "bracket"'
)
}
let parsedBrackets: unknown
try {
parsedBrackets = JSON.parse(params.retentionBrackets)
} catch {
parsedBrackets = undefined
}
if (!Array.isArray(parsedBrackets)) {
throw new Error(
'Amplitude Retention: "retentionBrackets" must be a valid JSON array of day ranges, e.g. [[0,4]]'
)
}
url.searchParams.set('rb', JSON.stringify(parsedBrackets))
}
if (params.interval) url.searchParams.set('i', params.interval)
if (params.groupBy) url.searchParams.set('g', params.groupBy)
if (params.segment) url.searchParams.set('s', params.segment)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Basic ${btoa(`${params.apiKey}:${params.secretKey}`)}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || `Amplitude Retention API error: ${response.status}`)
}
const result = data.data ?? {}
return {
success: true,
output: {
series: result.series ?? [],
seriesMeta: result.seriesMeta ?? [],
},
}
},
outputs: {
series: {
type: 'array',
description:
'Retention data series [{dates, values: {<date>: [{count, outof, incomplete}]}, combined: [{count, outof, incomplete}]}]',
items: {
type: 'json',
properties: {
dates: { type: 'array', description: 'Cohort dates', items: { type: 'string' } },
values: { type: 'json', description: 'Per-cohort-date retention counts keyed by date' },
combined: {
type: 'json',
description: 'Deduplicated aggregate retention across all cohorts',
},
},
},
},
seriesMeta: {
type: 'array',
description: 'Segment/event index metadata for each series entry',
items: { type: 'json' },
},
},
}
+10 -3
View File
@@ -1,4 +1,5 @@
import type { AmplitudeSendEventParams, AmplitudeSendEventResponse } from '@/tools/amplitude/types'
import { getIngestionHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const sendEventTool: ToolConfig<AmplitudeSendEventParams, AmplitudeSendEventResponse> = {
@@ -123,10 +124,16 @@ export const sendEventTool: ToolConfig<AmplitudeSendEventParams, AmplitudeSendEv
visibility: 'user-or-llm',
description: 'Revenue type (e.g., "purchase", "refund")',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: 'https://api2.amplitude.com/2/httpapi',
url: (params) => `${getIngestionHost(params.dataResidency)}/2/httpapi`,
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
@@ -149,8 +156,8 @@ export const sendEventTool: ToolConfig<AmplitudeSendEventParams, AmplitudeSendEv
if (params.price) event.price = Number(params.price)
if (params.quantity) event.quantity = Number(params.quantity)
if (params.revenue) event.revenue = Number(params.revenue)
if (params.productId) event.product_id = params.productId
if (params.revenueType) event.revenue_type = params.revenueType
if (params.productId) event.productId = params.productId
if (params.revenueType) event.revenueType = params.revenueType
if (params.eventProperties) {
try {
+97 -2
View File
@@ -5,6 +5,8 @@ import type { ToolResponse } from '@/tools/types'
*/
interface AmplitudeApiKeyParams {
apiKey: string
/** Data residency region: "us" (default) or "eu". */
dataResidency?: string
}
/**
@@ -13,6 +15,8 @@ interface AmplitudeApiKeyParams {
interface AmplitudeBasicAuthParams {
apiKey: string
secretKey: string
/** Data residency region: "us" (default) or "eu". */
dataResidency?: string
}
/**
@@ -126,6 +130,8 @@ export interface AmplitudeUserActivityResponse extends ToolResponse {
numSessions: number | null
platform: string | null
country: string | null
firstUsed: string | null
lastUsed: string | null
} | null
}
}
@@ -162,7 +168,14 @@ export interface AmplitudeEventSegmentationParams extends AmplitudeBasicAuthPara
metric?: string
interval?: string
groupBy?: string
groupBy2?: string
limit?: string
/** JSON array of filter objects applied to the event (subprop_type, subprop_key, subprop_op, subprop_value). */
filters?: string
/** Required when metric is "formula", e.g. "UNIQUES(A)/UNIQUES(B)". */
formula?: string
/** JSON segment definition(s) applied to the query. */
segment?: string
}
export interface AmplitudeEventSegmentationResponse extends ToolResponse {
@@ -182,6 +195,10 @@ export interface AmplitudeGetActiveUsersParams extends AmplitudeBasicAuthParams
end: string
metric?: string
interval?: string
/** Property to group by. */
groupBy?: string
/** JSON segment definition(s) applied to the query. */
segment?: string
}
export interface AmplitudeGetActiveUsersResponse extends ToolResponse {
@@ -218,6 +235,8 @@ export interface AmplitudeListEventsResponse extends ToolResponse {
totals: number
hidden: boolean
deleted: boolean
nonActive: boolean
flowHidden: boolean
}>
}
}
@@ -230,12 +249,88 @@ export interface AmplitudeGetRevenueParams extends AmplitudeBasicAuthParams {
end: string
metric?: string
interval?: string
/** Property to group by (limit: one). */
groupBy?: string
/** JSON segment definition(s) applied to the query. */
segment?: string
}
export interface AmplitudeGetRevenueResponse extends ToolResponse {
output: {
series: unknown[]
series: Array<{
dates: string[]
values: Record<
string,
{
count: number
paid: number
total_amount: number
[dayKey: string]: number
}
>
}>
seriesLabels: string[]
xValues: string[]
}
}
/**
* Funnel Analysis params (Dashboard REST API).
*/
export interface AmplitudeFunnelsParams extends AmplitudeBasicAuthParams {
/** JSON array of event objects, one per funnel step, in order. */
events: string
start: string
end: string
mode?: string
userType?: string
interval?: string
conversionWindowSeconds?: string
groupBy?: string
limit?: string
segment?: string
}
export interface AmplitudeFunnelsResponse extends ToolResponse {
output: {
funnels: Array<{
stepByStep: number[]
cumulative: number[]
cumulativeRaw: number[]
medianTransTimes: number[]
avgTransTimes: number[]
events: string[]
dayFunnels: {
series: number[][]
xValues: string[]
} | null
}>
}
}
/**
* Retention Analysis params (Dashboard REST API).
*/
export interface AmplitudeRetentionParams extends AmplitudeBasicAuthParams {
/** Starting event JSON object. Use event_type "_new" or "_active". */
startEvent: string
/** Returning event JSON object. Use event_type "_all" or "_active". */
returnEvent: string
start: string
end: string
retentionMode?: string
retentionBrackets?: string
interval?: string
groupBy?: string
segment?: string
}
export interface AmplitudeRetentionResponse extends ToolResponse {
output: {
series: Array<{
dates: string[]
values: Record<string, Array<{ count: number; outof: number; incomplete: boolean }>>
combined: Array<{ count: number; outof: number; incomplete: boolean }>
}>
seriesMeta: Array<{ segmentIndex: number; eventIndex: number }>
}
}
+12 -1
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeUserActivityParams,
AmplitudeUserActivityResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const userActivityTool: ToolConfig<
@@ -50,11 +51,17 @@ export const userActivityTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Sort direction: "latest" or "earliest" (default: latest)',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL('https://amplitude.com/api/2/useractivity')
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/useractivity`)
url.searchParams.set('user', params.amplitudeId.trim())
if (params.offset) url.searchParams.set('offset', params.offset)
if (params.limit) url.searchParams.set('limit', params.limit)
@@ -97,6 +104,8 @@ export const userActivityTool: ToolConfig<
numSessions: (ud.num_sessions as number) ?? null,
platform: (ud.platform as string) ?? null,
country: (ud.country as string) ?? null,
firstUsed: (ud.first_used as string) ?? null,
lastUsed: (ud.last_used as string) ?? null,
}
: null
@@ -138,6 +147,8 @@ export const userActivityTool: ToolConfig<
numSessions: { type: 'number', description: 'Total session count' },
platform: { type: 'string', description: 'Primary platform' },
country: { type: 'string', description: 'Country' },
firstUsed: { type: 'string', description: 'Date the user first appeared' },
lastUsed: { type: 'string', description: 'Date of most recent user activity' },
},
},
},
+1 -1
View File
@@ -9,7 +9,7 @@ export const userProfileTool: ToolConfig<AmplitudeUserProfileParams, AmplitudeUs
id: 'amplitude_user_profile',
name: 'Amplitude User Profile',
description:
'Get a user profile including properties, cohort memberships, and computed properties.',
'Get a user profile including properties, cohort memberships, and computed properties. Not available for EU data-residency projects.',
version: '1.0.0',
params: {
+8 -1
View File
@@ -2,6 +2,7 @@ import type {
AmplitudeUserSearchParams,
AmplitudeUserSearchResponse,
} from '@/tools/amplitude/types'
import { getDashboardHost } from '@/tools/amplitude/utils'
import type { ToolConfig } from '@/tools/types'
export const userSearchTool: ToolConfig<AmplitudeUserSearchParams, AmplitudeUserSearchResponse> = {
@@ -30,11 +31,17 @@ export const userSearchTool: ToolConfig<AmplitudeUserSearchParams, AmplitudeUser
visibility: 'user-or-llm',
description: 'User ID, Device ID, or Amplitude ID to search for',
},
dataResidency: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data residency region: "us" (default) or "eu"',
},
},
request: {
url: (params) => {
const url = new URL('https://amplitude.com/api/2/usersearch')
const url = new URL(`${getDashboardHost(params.dataResidency)}/api/2/usersearch`)
url.searchParams.set('user', params.user.trim())
return url.toString()
},
+12
View File
@@ -0,0 +1,12 @@
/**
* Amplitude hosts differ by data residency region. EU-region projects must send
* requests to the `eu.amplitude.com` hosts or the API rejects the request.
* See https://amplitude.com/docs/apis/analytics/http-v2#eu-residency-server-url
*/
export function getIngestionHost(dataResidency?: string): string {
return dataResidency === 'eu' ? 'https://api.eu.amplitude.com' : 'https://api2.amplitude.com'
}
export function getDashboardHost(dataResidency?: string): string {
return dataResidency === 'eu' ? 'https://analytics.eu.amplitude.com' : 'https://amplitude.com'
}
+4
View File
@@ -111,12 +111,14 @@ import {
} from '@/tools/algolia'
import {
amplitudeEventSegmentationTool,
amplitudeFunnelsTool,
amplitudeGetActiveUsersTool,
amplitudeGetRevenueTool,
amplitudeGroupIdentifyTool,
amplitudeIdentifyUserTool,
amplitudeListEventsTool,
amplitudeRealtimeActiveUsersTool,
amplitudeRetentionTool,
amplitudeSendEventTool,
amplitudeUserActivityTool,
amplitudeUserProfileTool,
@@ -4336,6 +4338,8 @@ export const tools: Record<string, ToolConfig> = {
amplitude_realtime_active_users: amplitudeRealtimeActiveUsersTool,
amplitude_list_events: amplitudeListEventsTool,
amplitude_get_revenue: amplitudeGetRevenueTool,
amplitude_funnels: amplitudeFunnelsTool,
amplitude_retention: amplitudeRetentionTool,
arxiv_get_author_papers: arxivGetAuthorPapersTool,
arxiv_get_paper: arxivGetPaperTool,
arxiv_search: arxivSearchTool,