chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import type {
|
||||
AmplitudeEventSegmentationParams,
|
||||
AmplitudeEventSegmentationResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getDashboardHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const eventSegmentationTool: ToolConfig<
|
||||
AmplitudeEventSegmentationParams,
|
||||
AmplitudeEventSegmentationResponse
|
||||
> = {
|
||||
id: 'amplitude_event_segmentation',
|
||||
name: 'Amplitude Event Segmentation',
|
||||
description:
|
||||
'Query event analytics data with segmentation. Get event counts, uniques, averages, and more.',
|
||||
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',
|
||||
},
|
||||
eventType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event type name to analyze',
|
||||
},
|
||||
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',
|
||||
},
|
||||
metric: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Metric type: uniques, totals, pct_dau, average, histogram, sums, value_avg, or formula (default: uniques)',
|
||||
},
|
||||
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 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(`${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',
|
||||
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 Event Segmentation API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const result = data.data ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: result.series ?? [],
|
||||
seriesLabels: result.seriesLabels ?? [],
|
||||
seriesCollapsed: result.seriesCollapsed ?? [],
|
||||
xValues: result.xValues ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'json',
|
||||
description: 'Time-series data arrays indexed by series',
|
||||
},
|
||||
seriesLabels: {
|
||||
type: 'array',
|
||||
description: 'Labels for each data series',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
seriesCollapsed: {
|
||||
type: 'json',
|
||||
description: 'Collapsed aggregate totals per series',
|
||||
},
|
||||
xValues: {
|
||||
type: 'array',
|
||||
description: 'Date values for the x-axis',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
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')
|
||||
}
|
||||
if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isRecordLike)) {
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import type {
|
||||
AmplitudeGetActiveUsersParams,
|
||||
AmplitudeGetActiveUsersResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getDashboardHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getActiveUsersTool: ToolConfig<
|
||||
AmplitudeGetActiveUsersParams,
|
||||
AmplitudeGetActiveUsersResponse
|
||||
> = {
|
||||
id: 'amplitude_get_active_users',
|
||||
name: 'Amplitude Get Active Users',
|
||||
description: 'Get active or new user counts over a date range from the Dashboard REST API.',
|
||||
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',
|
||||
},
|
||||
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',
|
||||
},
|
||||
metric: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Metric type: "active" or "new" (default: active)',
|
||||
},
|
||||
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 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(`${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',
|
||||
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 Active Users API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const result = data.data ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: result.series ?? [],
|
||||
seriesMeta: result.seriesMeta ?? [],
|
||||
xValues: result.xValues ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'json',
|
||||
description: 'Array of data series with user counts per time interval',
|
||||
},
|
||||
seriesMeta: {
|
||||
type: 'array',
|
||||
description: 'Metadata labels for each data series (e.g., segment names)',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
xValues: {
|
||||
type: 'array',
|
||||
description: 'Date values for the x-axis',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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> = {
|
||||
id: 'amplitude_get_revenue',
|
||||
name: 'Amplitude Get Revenue',
|
||||
description: 'Get revenue LTV data including ARPU, ARPPU, total revenue, and paying user counts.',
|
||||
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',
|
||||
},
|
||||
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',
|
||||
},
|
||||
metric: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Metric: 0 (ARPU), 1 (ARPPU), 2 (Total Revenue), 3 (Paying Users)',
|
||||
},
|
||||
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 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(`${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',
|
||||
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 Revenue API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const result = data.data ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: result.series ?? [],
|
||||
seriesLabels: result.seriesLabels ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
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' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import type {
|
||||
AmplitudeGroupIdentifyParams,
|
||||
AmplitudeGroupIdentifyResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getIngestionHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const groupIdentifyTool: ToolConfig<
|
||||
AmplitudeGroupIdentifyParams,
|
||||
AmplitudeGroupIdentifyResponse
|
||||
> = {
|
||||
id: 'amplitude_group_identify',
|
||||
name: 'Amplitude Group Identify',
|
||||
description:
|
||||
'Set group-level properties in Amplitude. Supports $set, $setOnce, $add, $append, $unset operations.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Amplitude API Key',
|
||||
},
|
||||
groupType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group classification (e.g., "company", "org_id")',
|
||||
},
|
||||
groupValue: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Specific group identifier (e.g., "Acme Corp")',
|
||||
},
|
||||
groupProperties: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
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: (params) => `${getIngestionHost(params.dataResidency)}/groupidentify`,
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}),
|
||||
body: (params) => {
|
||||
let groupProperties: Record<string, unknown> = {}
|
||||
try {
|
||||
groupProperties = JSON.parse(params.groupProperties)
|
||||
} catch {
|
||||
groupProperties = {}
|
||||
}
|
||||
|
||||
const identification = [
|
||||
{
|
||||
group_type: params.groupType,
|
||||
group_value: params.groupValue,
|
||||
group_properties: groupProperties,
|
||||
},
|
||||
]
|
||||
|
||||
const body = new URLSearchParams({
|
||||
api_key: params.apiKey,
|
||||
identification: JSON.stringify(identification),
|
||||
})
|
||||
|
||||
return body.toString()
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const text = await response.text()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Amplitude Group Identify API error: ${text}`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
code: response.status,
|
||||
message: text || null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
code: {
|
||||
type: 'number',
|
||||
description: 'HTTP response status code',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Response message',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type {
|
||||
AmplitudeIdentifyUserParams,
|
||||
AmplitudeIdentifyUserResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getIngestionHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const identifyUserTool: ToolConfig<
|
||||
AmplitudeIdentifyUserParams,
|
||||
AmplitudeIdentifyUserResponse
|
||||
> = {
|
||||
id: 'amplitude_identify_user',
|
||||
name: 'Amplitude Identify User',
|
||||
description:
|
||||
'Set user properties in Amplitude using the Identify API. Supports $set, $setOnce, $add, $append, $unset operations.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Amplitude API Key',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID (required if no device_id)',
|
||||
},
|
||||
deviceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Device ID (required if no user_id)',
|
||||
},
|
||||
userProperties: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
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: (params) => `${getIngestionHost(params.dataResidency)}/identify`,
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}),
|
||||
body: (params) => {
|
||||
const identification: Record<string, unknown> = {}
|
||||
|
||||
if (params.userId) identification.user_id = params.userId
|
||||
if (params.deviceId) identification.device_id = params.deviceId
|
||||
|
||||
try {
|
||||
identification.user_properties = JSON.parse(params.userProperties)
|
||||
} catch {
|
||||
identification.user_properties = {}
|
||||
}
|
||||
|
||||
const body = new URLSearchParams({
|
||||
api_key: params.apiKey,
|
||||
identification: JSON.stringify([identification]),
|
||||
})
|
||||
|
||||
return body.toString()
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const text = await response.text()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Amplitude Identify API error: ${text}`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
code: response.status,
|
||||
message: text || null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
code: {
|
||||
type: 'number',
|
||||
description: 'HTTP response status code',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Response message',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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'
|
||||
import { userSearchTool } from '@/tools/amplitude/user_search'
|
||||
|
||||
export const amplitudeSendEventTool = sendEventTool
|
||||
export const amplitudeIdentifyUserTool = identifyUserTool
|
||||
export const amplitudeGroupIdentifyTool = groupIdentifyTool
|
||||
export const amplitudeUserSearchTool = userSearchTool
|
||||
export const amplitudeUserActivityTool = userActivityTool
|
||||
export const amplitudeUserProfileTool = userProfileTool
|
||||
export const amplitudeEventSegmentationTool = eventSegmentationTool
|
||||
export const amplitudeGetActiveUsersTool = getActiveUsersTool
|
||||
export const amplitudeRealtimeActiveUsersTool = realtimeActiveUsersTool
|
||||
export const amplitudeListEventsTool = listEventsTool
|
||||
export const amplitudeGetRevenueTool = getRevenueTool
|
||||
export const amplitudeFunnelsTool = funnelsTool
|
||||
export const amplitudeRetentionTool = retentionTool
|
||||
@@ -0,0 +1,96 @@
|
||||
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> = {
|
||||
id: 'amplitude_list_events',
|
||||
name: 'Amplitude List Events',
|
||||
description:
|
||||
'List all event types in the Amplitude project with their weekly totals and unique counts.',
|
||||
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',
|
||||
},
|
||||
dataResidency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Data residency region: "us" (default) or "eu"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${getDashboardHost(params.dataResidency)}/api/2/events/list`,
|
||||
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 List Events API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const events = (data.data ?? []).map(
|
||||
(e: Record<string, unknown>) =>
|
||||
({
|
||||
value: (e.value as string) ?? '',
|
||||
displayName: (e.display as string) ?? null,
|
||||
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
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'List of event types in the project',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: { type: 'string', description: 'Event type name' },
|
||||
displayName: { type: 'string', description: 'Event display name' },
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type {
|
||||
AmplitudeRealtimeActiveUsersParams,
|
||||
AmplitudeRealtimeActiveUsersResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getDashboardHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const realtimeActiveUsersTool: ToolConfig<
|
||||
AmplitudeRealtimeActiveUsersParams,
|
||||
AmplitudeRealtimeActiveUsersResponse
|
||||
> = {
|
||||
id: 'amplitude_realtime_active_users',
|
||||
name: 'Amplitude Real-time Active Users',
|
||||
description: 'Get real-time active user counts at 5-minute granularity for the last 2 days.',
|
||||
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',
|
||||
},
|
||||
dataResidency: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Data residency region: "us" (default) or "eu"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `${getDashboardHost(params.dataResidency)}/api/2/realtime`,
|
||||
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 Real-time API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const result = data.data ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
series: result.series ?? [],
|
||||
seriesLabels: result.seriesLabels ?? [],
|
||||
xValues: result.xValues ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
series: {
|
||||
type: 'json',
|
||||
description: 'Array of data series with active user counts at 5-minute intervals',
|
||||
},
|
||||
seriesLabels: {
|
||||
type: 'array',
|
||||
description: 'Labels for each series (e.g., "Today", "Yesterday")',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
xValues: {
|
||||
type: 'array',
|
||||
description: 'Time values for the x-axis (e.g., "15:00", "15:05")',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
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> = {
|
||||
id: 'amplitude_send_event',
|
||||
name: 'Amplitude Send Event',
|
||||
description: 'Track an event in Amplitude using the HTTP V2 API.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Amplitude API Key',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID (required if no device_id)',
|
||||
},
|
||||
deviceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Device ID (required if no user_id)',
|
||||
},
|
||||
eventType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the event (e.g., "page_view", "purchase")',
|
||||
},
|
||||
eventProperties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON object of custom event properties',
|
||||
},
|
||||
userProperties: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object of user properties to set (supports $set, $setOnce, $add, $append, $unset)',
|
||||
},
|
||||
time: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event timestamp in milliseconds since epoch',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Session start time in milliseconds since epoch',
|
||||
},
|
||||
insertId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unique ID for deduplication (within 7-day window)',
|
||||
},
|
||||
appVersion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application version string',
|
||||
},
|
||||
platform: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Platform (e.g., "Web", "iOS", "Android")',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Two-letter country code',
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Language code (e.g., "en")',
|
||||
},
|
||||
ip: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IP address for geo-location',
|
||||
},
|
||||
price: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Price of the item purchased',
|
||||
},
|
||||
quantity: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Quantity of items purchased',
|
||||
},
|
||||
revenue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Revenue amount',
|
||||
},
|
||||
productId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Product identifier',
|
||||
},
|
||||
revenueType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
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: (params) => `${getIngestionHost(params.dataResidency)}/2/httpapi`,
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const event: Record<string, unknown> = {
|
||||
event_type: params.eventType,
|
||||
}
|
||||
|
||||
if (params.userId) event.user_id = params.userId
|
||||
if (params.deviceId) event.device_id = params.deviceId
|
||||
if (params.time) event.time = Number(params.time)
|
||||
if (params.sessionId) event.session_id = Number(params.sessionId)
|
||||
if (params.insertId) event.insert_id = params.insertId
|
||||
if (params.appVersion) event.app_version = params.appVersion
|
||||
if (params.platform) event.platform = params.platform
|
||||
if (params.country) event.country = params.country
|
||||
if (params.language) event.language = params.language
|
||||
if (params.ip) event.ip = params.ip
|
||||
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.productId = params.productId
|
||||
if (params.revenueType) event.revenueType = params.revenueType
|
||||
|
||||
if (params.eventProperties) {
|
||||
try {
|
||||
event.event_properties = JSON.parse(params.eventProperties)
|
||||
} catch {
|
||||
event.event_properties = {}
|
||||
}
|
||||
}
|
||||
|
||||
if (params.userProperties) {
|
||||
try {
|
||||
event.user_properties = JSON.parse(params.userProperties)
|
||||
} catch {
|
||||
event.user_properties = {}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
api_key: params.apiKey,
|
||||
events: [event],
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.code !== 200) {
|
||||
throw new Error(data.error || `Amplitude API error: code ${data.code}`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
code: data.code ?? 200,
|
||||
eventsIngested: data.events_ingested ?? 0,
|
||||
payloadSizeBytes: data.payload_size_bytes ?? 0,
|
||||
serverUploadTime: data.server_upload_time ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
code: {
|
||||
type: 'number',
|
||||
description: 'Response code (200 for success)',
|
||||
},
|
||||
eventsIngested: {
|
||||
type: 'number',
|
||||
description: 'Number of events ingested',
|
||||
},
|
||||
payloadSizeBytes: {
|
||||
type: 'number',
|
||||
description: 'Size of the payload in bytes',
|
||||
},
|
||||
serverUploadTime: {
|
||||
type: 'number',
|
||||
description: 'Server upload timestamp',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Base params shared by endpoints using API key in body.
|
||||
*/
|
||||
interface AmplitudeApiKeyParams {
|
||||
apiKey: string
|
||||
/** Data residency region: "us" (default) or "eu". */
|
||||
dataResidency?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Base params shared by endpoints using Basic Auth (api_key:secret_key).
|
||||
*/
|
||||
interface AmplitudeBasicAuthParams {
|
||||
apiKey: string
|
||||
secretKey: string
|
||||
/** Data residency region: "us" (default) or "eu". */
|
||||
dataResidency?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Event params (HTTP V2 API).
|
||||
*/
|
||||
export interface AmplitudeSendEventParams extends AmplitudeApiKeyParams {
|
||||
userId?: string
|
||||
deviceId?: string
|
||||
eventType: string
|
||||
eventProperties?: string
|
||||
userProperties?: string
|
||||
time?: string
|
||||
sessionId?: string
|
||||
insertId?: string
|
||||
appVersion?: string
|
||||
platform?: string
|
||||
country?: string
|
||||
language?: string
|
||||
ip?: string
|
||||
price?: string
|
||||
quantity?: string
|
||||
revenue?: string
|
||||
productId?: string
|
||||
revenueType?: string
|
||||
}
|
||||
|
||||
export interface AmplitudeSendEventResponse extends ToolResponse {
|
||||
output: {
|
||||
code: number
|
||||
eventsIngested: number
|
||||
payloadSizeBytes: number
|
||||
serverUploadTime: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify User params (Identify API).
|
||||
*/
|
||||
export interface AmplitudeIdentifyUserParams extends AmplitudeApiKeyParams {
|
||||
userId?: string
|
||||
deviceId?: string
|
||||
userProperties: string
|
||||
}
|
||||
|
||||
export interface AmplitudeIdentifyUserResponse extends ToolResponse {
|
||||
output: {
|
||||
code: number
|
||||
message: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group Identify params (Group Identify API).
|
||||
*/
|
||||
export interface AmplitudeGroupIdentifyParams extends AmplitudeApiKeyParams {
|
||||
groupType: string
|
||||
groupValue: string
|
||||
groupProperties: string
|
||||
}
|
||||
|
||||
export interface AmplitudeGroupIdentifyResponse extends ToolResponse {
|
||||
output: {
|
||||
code: number
|
||||
message: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User Search params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeUserSearchParams extends AmplitudeBasicAuthParams {
|
||||
user: string
|
||||
}
|
||||
|
||||
export interface AmplitudeUserSearchResponse extends ToolResponse {
|
||||
output: {
|
||||
matches: Array<{
|
||||
amplitudeId: number
|
||||
userId: string | null
|
||||
}>
|
||||
type: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User Activity params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeUserActivityParams extends AmplitudeBasicAuthParams {
|
||||
amplitudeId: string
|
||||
offset?: string
|
||||
limit?: string
|
||||
direction?: string
|
||||
}
|
||||
|
||||
export interface AmplitudeUserActivityResponse extends ToolResponse {
|
||||
output: {
|
||||
events: Array<{
|
||||
eventType: string
|
||||
eventTime: string
|
||||
eventProperties: Record<string, unknown>
|
||||
userProperties: Record<string, unknown>
|
||||
sessionId: number | null
|
||||
platform: string | null
|
||||
country: string | null
|
||||
city: string | null
|
||||
}>
|
||||
userData: {
|
||||
userId: string | null
|
||||
canonicalAmplitudeId: number | null
|
||||
numEvents: number | null
|
||||
numSessions: number | null
|
||||
platform: string | null
|
||||
country: string | null
|
||||
firstUsed: string | null
|
||||
lastUsed: string | null
|
||||
} | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User Profile params (User Profile API).
|
||||
*/
|
||||
export interface AmplitudeUserProfileParams {
|
||||
secretKey: string
|
||||
userId?: string
|
||||
deviceId?: string
|
||||
getAmpProps?: string
|
||||
getCohortIds?: string
|
||||
getComputations?: string
|
||||
}
|
||||
|
||||
export interface AmplitudeUserProfileResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string | null
|
||||
deviceId: string | null
|
||||
ampProps: Record<string, unknown> | null
|
||||
cohortIds: string[] | null
|
||||
computations: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Segmentation params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeEventSegmentationParams extends AmplitudeBasicAuthParams {
|
||||
eventType: string
|
||||
start: string
|
||||
end: string
|
||||
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 {
|
||||
output: {
|
||||
series: unknown[]
|
||||
seriesLabels: string[]
|
||||
seriesCollapsed: unknown[]
|
||||
xValues: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Active Users params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeGetActiveUsersParams extends AmplitudeBasicAuthParams {
|
||||
start: string
|
||||
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 {
|
||||
output: {
|
||||
series: number[][]
|
||||
seriesMeta: string[]
|
||||
xValues: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Real-time Active Users params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeRealtimeActiveUsersParams extends AmplitudeBasicAuthParams {}
|
||||
|
||||
export interface AmplitudeRealtimeActiveUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
series: number[][]
|
||||
seriesLabels: string[]
|
||||
xValues: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List Events params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeListEventsParams extends AmplitudeBasicAuthParams {}
|
||||
|
||||
export interface AmplitudeListEventsResponse extends ToolResponse {
|
||||
output: {
|
||||
events: Array<{
|
||||
value: string
|
||||
displayName: string | null
|
||||
totals: number
|
||||
hidden: boolean
|
||||
deleted: boolean
|
||||
nonActive: boolean
|
||||
flowHidden: boolean
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Revenue params (Dashboard REST API).
|
||||
*/
|
||||
export interface AmplitudeGetRevenueParams extends AmplitudeBasicAuthParams {
|
||||
start: string
|
||||
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: Array<{
|
||||
dates: string[]
|
||||
values: Record<
|
||||
string,
|
||||
{
|
||||
count: number
|
||||
paid: number
|
||||
total_amount: number
|
||||
[dayKey: string]: number
|
||||
}
|
||||
>
|
||||
}>
|
||||
seriesLabels: 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 }>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import type {
|
||||
AmplitudeUserActivityParams,
|
||||
AmplitudeUserActivityResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import { getDashboardHost } from '@/tools/amplitude/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const userActivityTool: ToolConfig<
|
||||
AmplitudeUserActivityParams,
|
||||
AmplitudeUserActivityResponse
|
||||
> = {
|
||||
id: 'amplitude_user_activity',
|
||||
name: 'Amplitude User Activity',
|
||||
description: 'Get the event stream for a specific user by their Amplitude ID.',
|
||||
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',
|
||||
},
|
||||
amplitudeId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Amplitude internal user ID',
|
||||
},
|
||||
offset: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Offset for pagination (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of events to return (default 1000, max 1000)',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
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(`${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)
|
||||
if (params.direction) url.searchParams.set('direction', params.direction)
|
||||
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 User Activity API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const events = (data.events ?? []).map(
|
||||
(e: Record<string, unknown>) =>
|
||||
({
|
||||
eventType: (e.event_type as string) ?? '',
|
||||
eventTime: (e.event_time as string) ?? '',
|
||||
eventProperties: (e.event_properties as Record<string, unknown>) ?? {},
|
||||
userProperties: (e.user_properties as Record<string, unknown>) ?? {},
|
||||
sessionId: (e.session_id as number) ?? null,
|
||||
platform: (e.platform as string) ?? null,
|
||||
country: (e.country as string) ?? null,
|
||||
city: (e.city as string) ?? null,
|
||||
}) as const
|
||||
)
|
||||
|
||||
const ud = data.userData as Record<string, unknown> | undefined
|
||||
const userData = ud
|
||||
? {
|
||||
userId: (ud.user_id as string) ?? null,
|
||||
canonicalAmplitudeId: (ud.canonical_amplitude_id as number) ?? null,
|
||||
numEvents: (ud.num_events as number) ?? null,
|
||||
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
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
userData,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'List of user events',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
eventType: { type: 'string', description: 'Type of event' },
|
||||
eventTime: { type: 'string', description: 'Event timestamp' },
|
||||
eventProperties: { type: 'json', description: 'Custom event properties' },
|
||||
userProperties: { type: 'json', description: 'User properties at event time' },
|
||||
sessionId: { type: 'number', description: 'Session ID' },
|
||||
platform: { type: 'string', description: 'Platform' },
|
||||
country: { type: 'string', description: 'Country' },
|
||||
city: { type: 'string', description: 'City' },
|
||||
},
|
||||
},
|
||||
},
|
||||
userData: {
|
||||
type: 'json',
|
||||
description: 'User metadata',
|
||||
optional: true,
|
||||
properties: {
|
||||
userId: { type: 'string', description: 'External user ID' },
|
||||
canonicalAmplitudeId: { type: 'number', description: 'Canonical Amplitude ID' },
|
||||
numEvents: { type: 'number', description: 'Total event count' },
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import type {
|
||||
AmplitudeUserProfileParams,
|
||||
AmplitudeUserProfileResponse,
|
||||
} from '@/tools/amplitude/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const userProfileTool: ToolConfig<AmplitudeUserProfileParams, AmplitudeUserProfileResponse> =
|
||||
{
|
||||
id: 'amplitude_user_profile',
|
||||
name: 'Amplitude User Profile',
|
||||
description:
|
||||
'Get a user profile including properties, cohort memberships, and computed properties. Not available for EU data-residency projects.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Amplitude Secret Key',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External user ID (required if no device_id)',
|
||||
},
|
||||
deviceId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Device ID (required if no user_id)',
|
||||
},
|
||||
getAmpProps: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include Amplitude user properties (true/false, default: false)',
|
||||
},
|
||||
getCohortIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include cohort IDs the user belongs to (true/false, default: false)',
|
||||
},
|
||||
getComputations: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include computed user properties (true/false, default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://profile-api.amplitude.com/v1/userprofile')
|
||||
if (params.userId) url.searchParams.set('user_id', params.userId.trim())
|
||||
if (params.deviceId) url.searchParams.set('device_id', params.deviceId.trim())
|
||||
if (params.getAmpProps) url.searchParams.set('get_amp_props', params.getAmpProps)
|
||||
if (params.getCohortIds) url.searchParams.set('get_cohort_ids', params.getCohortIds)
|
||||
if (params.getComputations) url.searchParams.set('get_computations', params.getComputations)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Api-Key ${params.secretKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || `Amplitude User Profile API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const userData = data.userData ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: (userData.user_id as string) ?? null,
|
||||
deviceId: (userData.device_id as string) ?? null,
|
||||
ampProps: (userData.amp_props as Record<string, unknown>) ?? null,
|
||||
cohortIds: (userData.cohort_ids as string[]) ?? null,
|
||||
computations: (userData.computations as Record<string, unknown>) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: {
|
||||
type: 'string',
|
||||
description: 'External user ID',
|
||||
optional: true,
|
||||
},
|
||||
deviceId: {
|
||||
type: 'string',
|
||||
description: 'Device ID',
|
||||
optional: true,
|
||||
},
|
||||
ampProps: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Amplitude user properties (library, first_used, last_used, custom properties)',
|
||||
optional: true,
|
||||
},
|
||||
cohortIds: {
|
||||
type: 'array',
|
||||
description: 'List of cohort IDs the user belongs to',
|
||||
optional: true,
|
||||
items: { type: 'string' },
|
||||
},
|
||||
computations: {
|
||||
type: 'json',
|
||||
description: 'Computed user properties',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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> = {
|
||||
id: 'amplitude_user_search',
|
||||
name: 'Amplitude User Search',
|
||||
description:
|
||||
'Search for a user by User ID, Device ID, or Amplitude ID using the Dashboard REST API.',
|
||||
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',
|
||||
},
|
||||
user: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
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(`${getDashboardHost(params.dataResidency)}/api/2/usersearch`)
|
||||
url.searchParams.set('user', params.user.trim())
|
||||
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 User Search API error: ${response.status}`)
|
||||
}
|
||||
|
||||
const matches = (data.matches ?? []).map(
|
||||
(m: Record<string, unknown>) =>
|
||||
({
|
||||
amplitudeId: (m.amplitude_id as number) ?? 0,
|
||||
userId: (m.user_id as string) ?? null,
|
||||
}) as const
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
matches,
|
||||
type: (data.type as string) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
matches: {
|
||||
type: 'array',
|
||||
description: 'List of matching users',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
amplitudeId: { type: 'number', description: 'Amplitude internal user ID' },
|
||||
userId: { type: 'string', description: 'External user ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Match type (e.g., match_user_or_device_id)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
Reference in New Issue
Block a user