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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -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' },
},
},
}
+193
View File
@@ -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' },
},
},
}
+133
View File
@@ -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' },
},
},
}
+110
View File
@@ -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,
},
},
}
+106
View File
@@ -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,
},
},
}
+27
View File
@@ -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
+96
View File
@@ -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' },
},
},
}
+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' },
},
},
}
+221
View File
@@ -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',
},
},
}
+336
View File
@@ -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 }>
}
}
+155
View File
@@ -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' },
},
},
},
}
+120
View File
@@ -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,
},
},
}
+96
View File
@@ -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,
},
},
}
+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'
}