diff --git a/apps/sim/blocks/blocks/posthog.ts b/apps/sim/blocks/blocks/posthog.ts index 42c75de04c2..40fb4ecb07d 100644 --- a/apps/sim/blocks/blocks/posthog.ts +++ b/apps/sim/blocks/blocks/posthog.ts @@ -32,12 +32,15 @@ export const PostHogBlock: BlockConfig = { { label: 'List Insights', id: 'posthog_list_insights' }, { label: 'Get Insight', id: 'posthog_get_insight' }, { label: 'Create Insight', id: 'posthog_create_insight' }, + { label: 'Update Insight', id: 'posthog_update_insight' }, { label: 'List Dashboards', id: 'posthog_list_dashboards' }, { label: 'Get Dashboard', id: 'posthog_get_dashboard' }, + { label: 'Create Dashboard', id: 'posthog_create_dashboard' }, { label: 'List Actions', id: 'posthog_list_actions' }, { label: 'List Cohorts', id: 'posthog_list_cohorts' }, { label: 'Get Cohort', id: 'posthog_get_cohort' }, { label: 'Create Cohort', id: 'posthog_create_cohort' }, + { label: 'Update Cohort', id: 'posthog_update_cohort' }, { label: 'List Annotations', id: 'posthog_list_annotations' }, { label: 'Create Annotation', id: 'posthog_create_annotation' }, // Feature Management @@ -50,11 +53,13 @@ export const PostHogBlock: BlockConfig = { { label: 'List Experiments', id: 'posthog_list_experiments' }, { label: 'Get Experiment', id: 'posthog_get_experiment' }, { label: 'Create Experiment', id: 'posthog_create_experiment' }, + { label: 'Update Experiment', id: 'posthog_update_experiment' }, // User Engagement { label: 'List Surveys', id: 'posthog_list_surveys' }, { label: 'Get Survey', id: 'posthog_get_survey' }, { label: 'Create Survey', id: 'posthog_create_survey' }, { label: 'Update Survey', id: 'posthog_update_survey' }, + { label: 'Delete Survey', id: 'posthog_delete_survey' }, { label: 'List Session Recordings', id: 'posthog_list_session_recordings' }, { label: 'Get Session Recording', id: 'posthog_get_session_recording' }, { label: 'List Recording Playlists', id: 'posthog_list_recording_playlists' }, @@ -95,6 +100,7 @@ export const PostHogBlock: BlockConfig = { 'posthog_list_feature_flags', // Experiments 'posthog_create_experiment', + 'posthog_update_experiment', 'posthog_get_experiment', 'posthog_list_experiments', // Data Management @@ -113,12 +119,15 @@ export const PostHogBlock: BlockConfig = { 'posthog_list_insights', 'posthog_get_insight', 'posthog_create_insight', + 'posthog_update_insight', 'posthog_list_dashboards', 'posthog_get_dashboard', + 'posthog_create_dashboard', 'posthog_list_actions', 'posthog_list_cohorts', 'posthog_get_cohort', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_list_annotations', 'posthog_create_annotation', // Surveys & Recordings @@ -126,6 +135,73 @@ export const PostHogBlock: BlockConfig = { 'posthog_get_survey', 'posthog_create_survey', 'posthog_update_survey', + 'posthog_delete_survey', + 'posthog_list_session_recordings', + 'posthog_get_session_recording', + 'posthog_list_recording_playlists', + // Configuration + 'posthog_list_projects', + 'posthog_get_project', + 'posthog_list_organizations', + 'posthog_get_organization', + ], + }, + }, + { + id: 'host', + title: 'Self-Hosted Host', + type: 'short-input', + placeholder: 'posthog.mycompany.com', + condition: { + field: 'operation', + value: [ + // Feature Flags + 'posthog_create_feature_flag', + 'posthog_update_feature_flag', + 'posthog_delete_feature_flag', + 'posthog_get_feature_flag', + 'posthog_list_feature_flags', + // Experiments + 'posthog_create_experiment', + 'posthog_update_experiment', + 'posthog_get_experiment', + 'posthog_list_experiments', + // Data Management + 'posthog_list_property_definitions', + 'posthog_get_property_definition', + 'posthog_update_property_definition', + 'posthog_list_event_definitions', + 'posthog_get_event_definition', + 'posthog_update_event_definition', + // Core Operations + 'posthog_capture_event', + 'posthog_batch_events', + 'posthog_list_persons', + 'posthog_get_person', + 'posthog_delete_person', + 'posthog_query', + 'posthog_evaluate_flags', + // Analytics + 'posthog_list_insights', + 'posthog_get_insight', + 'posthog_create_insight', + 'posthog_update_insight', + 'posthog_list_dashboards', + 'posthog_get_dashboard', + 'posthog_create_dashboard', + 'posthog_list_actions', + 'posthog_list_cohorts', + 'posthog_get_cohort', + 'posthog_create_cohort', + 'posthog_update_cohort', + 'posthog_list_annotations', + 'posthog_create_annotation', + // Surveys & Recordings + 'posthog_list_surveys', + 'posthog_get_survey', + 'posthog_create_survey', + 'posthog_update_survey', + 'posthog_delete_survey', 'posthog_list_session_recordings', 'posthog_get_session_recording', 'posthog_list_recording_playlists', @@ -392,7 +468,7 @@ Return ONLY the JSON array.`, placeholder: 'HogQL query or JSON object', condition: { field: 'operation', - value: 'posthog_create_cohort', + value: ['posthog_create_cohort', 'posthog_update_cohort'], }, }, { @@ -431,7 +507,10 @@ Return ONLY the JSON array.`, title: 'Insight ID', type: 'short-input', placeholder: 'Insight ID', - condition: { field: 'operation', value: 'posthog_get_insight' }, + condition: { + field: 'operation', + value: ['posthog_get_insight', 'posthog_update_insight'], + }, required: true, }, { @@ -442,12 +521,28 @@ Return ONLY the JSON array.`, condition: { field: 'operation', value: 'posthog_get_dashboard' }, required: true, }, + { + id: 'pinned', + title: 'Pinned', + type: 'switch', + condition: { field: 'operation', value: 'posthog_create_dashboard' }, + }, + { + id: 'useTemplate', + title: 'Template', + type: 'short-input', + placeholder: 'Product analytics', + condition: { field: 'operation', value: 'posthog_create_dashboard' }, + }, { id: 'cohortId', title: 'Cohort ID', type: 'short-input', placeholder: 'Cohort ID', - condition: { field: 'operation', value: 'posthog_get_cohort' }, + condition: { + field: 'operation', + value: ['posthog_get_cohort', 'posthog_update_cohort'], + }, required: true, }, { @@ -470,7 +565,10 @@ Return ONLY the JSON array.`, title: 'Experiment ID', type: 'short-input', placeholder: 'Experiment ID', - condition: { field: 'operation', value: 'posthog_get_experiment' }, + condition: { + field: 'operation', + value: ['posthog_get_experiment', 'posthog_update_experiment'], + }, required: true, }, { @@ -480,7 +578,7 @@ Return ONLY the JSON array.`, placeholder: 'Survey ID', condition: { field: 'operation', - value: ['posthog_get_survey', 'posthog_update_survey'], + value: ['posthog_get_survey', 'posthog_update_survey', 'posthog_delete_survey'], }, required: true, }, @@ -525,13 +623,17 @@ Return ONLY the JSON array.`, field: 'operation', value: [ 'posthog_create_insight', + 'posthog_update_insight', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_create_annotation', 'posthog_create_feature_flag', 'posthog_update_feature_flag', 'posthog_create_experiment', + 'posthog_update_experiment', 'posthog_create_survey', 'posthog_update_survey', + 'posthog_create_dashboard', ], }, }, @@ -544,14 +646,18 @@ Return ONLY the JSON array.`, field: 'operation', value: [ 'posthog_create_insight', + 'posthog_update_insight', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_create_feature_flag', 'posthog_update_feature_flag', 'posthog_create_experiment', + 'posthog_update_experiment', 'posthog_create_survey', 'posthog_update_survey', 'posthog_update_event_definition', 'posthog_update_property_definition', + 'posthog_create_dashboard', ], }, wandConfig: { @@ -602,11 +708,12 @@ Return ONLY the description text.`, condition: { field: 'operation', value: [ - 'posthog_create_insight', 'posthog_create_feature_flag', 'posthog_update_feature_flag', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_create_experiment', + 'posthog_update_experiment', ], }, wandConfig: { @@ -648,21 +755,36 @@ Return ONLY the JSON object.`, title: 'Query (JSON)', type: 'long-input', placeholder: '{"kind": "HogQLQuery", "query": "SELECT ..."}', - condition: { field: 'operation', value: 'posthog_create_insight' }, + condition: { + field: 'operation', + value: ['posthog_create_insight', 'posthog_update_insight'], + }, }, { id: 'dashboards', title: 'Dashboard IDs (comma-separated)', type: 'short-input', placeholder: '123, 456, 789', - condition: { field: 'operation', value: 'posthog_create_insight' }, + condition: { + field: 'operation', + value: ['posthog_create_insight', 'posthog_update_insight'], + }, }, { id: 'insightTags', title: 'Tags (comma-separated)', type: 'short-input', placeholder: 'analytics, revenue, important', - condition: { field: 'operation', value: 'posthog_create_insight' }, + condition: { + field: 'operation', + value: ['posthog_create_insight', 'posthog_update_insight'], + }, + }, + { + id: 'favorited', + title: 'Favorited', + type: 'switch', + condition: { field: 'operation', value: 'posthog_update_insight' }, }, // Feature Flag fields @@ -703,14 +825,20 @@ Return ONLY the JSON object.`, title: 'Groups (JSON Array)', type: 'long-input', placeholder: '[{"properties": [...]}]', - condition: { field: 'operation', value: 'posthog_create_cohort' }, + condition: { field: 'operation', value: ['posthog_create_cohort', 'posthog_update_cohort'] }, }, { id: 'isStatic', title: 'Static Cohort', type: 'switch', value: () => 'false', - condition: { field: 'operation', value: 'posthog_create_cohort' }, + condition: { field: 'operation', value: ['posthog_create_cohort', 'posthog_update_cohort'] }, + }, + { + id: 'deleted', + title: 'Archive Cohort', + type: 'switch', + condition: { field: 'operation', value: 'posthog_update_cohort' }, }, // Annotation fields @@ -789,21 +917,29 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, title: 'Parameters (JSON)', type: 'long-input', placeholder: '{"minimum_detectable_effect": 5}', - condition: { field: 'operation', value: 'posthog_create_experiment' }, + condition: { + field: 'operation', + value: ['posthog_create_experiment', 'posthog_update_experiment'], + }, }, { - id: 'variants', - title: 'Variants (JSON)', - type: 'long-input', - placeholder: '{"control": 50, "test": 50}', - condition: { field: 'operation', value: 'posthog_create_experiment' }, + id: 'archived', + title: 'Archived', + type: 'switch', + condition: { + field: 'operation', + value: ['posthog_update_experiment', 'posthog_update_survey'], + }, }, { id: 'experimentStartDate', title: 'Start Date (ISO 8601)', type: 'short-input', placeholder: '2024-01-01T00:00:00Z', - condition: { field: 'operation', value: 'posthog_create_experiment' }, + condition: { + field: 'operation', + value: ['posthog_create_experiment', 'posthog_update_experiment'], + }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -823,7 +959,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, title: 'End Date (ISO 8601)', type: 'short-input', placeholder: '2024-12-31T23:59:59Z', - condition: { field: 'operation', value: 'posthog_create_experiment' }, + condition: { + field: 'operation', + value: ['posthog_create_experiment', 'posthog_update_experiment'], + }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -1096,7 +1235,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, placeholder: 'tag1, tag2, tag3', condition: { field: 'operation', - value: ['posthog_update_event_definition', 'posthog_update_property_definition'], + value: [ + 'posthog_update_event_definition', + 'posthog_update_property_definition', + 'posthog_create_dashboard', + ], }, }, @@ -1146,12 +1289,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 'posthog_list_insights', 'posthog_get_insight', 'posthog_create_insight', + 'posthog_update_insight', 'posthog_list_dashboards', 'posthog_get_dashboard', + 'posthog_create_dashboard', 'posthog_list_actions', 'posthog_list_cohorts', 'posthog_get_cohort', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_list_annotations', 'posthog_create_annotation', // Feature Management @@ -1164,11 +1310,13 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 'posthog_list_experiments', 'posthog_get_experiment', 'posthog_create_experiment', + 'posthog_update_experiment', // Engagement 'posthog_list_surveys', 'posthog_get_survey', 'posthog_create_survey', 'posthog_update_survey', + 'posthog_delete_survey', 'posthog_list_session_recordings', 'posthog_get_session_recording', 'posthog_list_recording_playlists', @@ -1222,11 +1370,19 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, params.property_type = params.propertyType } - if (params.operation === 'posthog_create_insight' && params.insightQuery) { + if ( + (params.operation === 'posthog_create_insight' || + params.operation === 'posthog_update_insight') && + params.insightQuery + ) { params.query = params.insightQuery } - if (params.operation === 'posthog_create_insight' && params.insightTags) { + if ( + (params.operation === 'posthog_create_insight' || + params.operation === 'posthog_update_insight') && + params.insightTags + ) { params.tags = params.insightTags } @@ -1234,7 +1390,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, params.distinctId = params.distinctIdFilter } - if (params.operation === 'posthog_create_experiment') { + if ( + params.operation === 'posthog_create_experiment' || + params.operation === 'posthog_update_experiment' + ) { if (params.experimentStartDate) { params.startDate = params.experimentStartDate } @@ -1272,6 +1431,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, region: { type: 'string', description: 'PostHog region (us or eu)' }, + host: { type: 'string', description: 'Self-hosted PostHog instance host' }, projectApiKey: { type: 'string', description: 'Project API key for public endpoints' }, apiKey: { type: 'string', description: 'Personal API key for private endpoints' }, projectId: { type: 'string', description: 'PostHog project ID' }, @@ -1304,13 +1464,22 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, active: { type: 'boolean', description: 'Whether flag is active' }, rolloutPercentage: { type: 'number', description: 'Rollout percentage (0-100)' }, groups: { type: 'string', description: 'Cohort groups as JSON' }, + isStatic: { type: 'boolean', description: 'Whether the cohort is static' }, + deleted: { type: 'boolean', description: 'Whether to archive (soft-delete) the cohort' }, content: { type: 'string', description: 'Annotation content' }, dateMarker: { type: 'string', description: 'Annotation date' }, scope: { type: 'string', description: 'Annotation scope' }, featureFlagKey: { type: 'string', description: 'Feature flag key for experiment' }, parameters: { type: 'string', description: 'Experiment parameters as JSON' }, + archived: { type: 'boolean', description: 'Whether to archive the experiment or survey' }, questions: { type: 'string', description: 'Survey questions as JSON array' }, surveyType: { type: 'string', description: 'Survey type (popover or api)' }, + insightQuery: { type: 'string', description: 'Insight query as JSON' }, + dashboards: { type: 'string', description: 'Comma-separated dashboard IDs' }, + insightTags: { type: 'string', description: 'Comma-separated insight tags' }, + favorited: { type: 'boolean', description: 'Whether the insight is favorited' }, + pinned: { type: 'boolean', description: 'Whether the dashboard is pinned' }, + useTemplate: { type: 'string', description: 'Dashboard template name to seed from' }, // List parameters limit: { type: 'number', description: 'Number of results to return' }, offset: { type: 'number', description: 'Number of results to skip' }, diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index 0bfc70c7c57..1f1590a9053 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -243,6 +243,17 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ return undefined }, }, + { + id: 'posthog-errors', + description: 'PostHog API error format with type/code/detail/attr fields', + examples: ['PostHog API'], + extract: (errorInfo) => { + const detail = errorInfo?.data?.detail + if (typeof detail !== 'string' || !detail.trim()) return undefined + const attr = errorInfo?.data?.attr + return typeof attr === 'string' && attr ? `${detail} (${attr})` : detail + }, + }, { id: 'plain-text-data', description: 'Plain text error response', @@ -320,6 +331,7 @@ export const ErrorExtractorId = { SOAP_FAULT: 'soap-fault', OAUTH_ERROR_DESCRIPTION: 'oauth-error-description', NESTED_ERROR_OBJECT: 'nested-error-object', + POSTHOG_ERRORS: 'posthog-errors', PLAIN_TEXT_DATA: 'plain-text-data', HTTP_STATUS_TEXT: 'http-status-text', } as const diff --git a/apps/sim/tools/posthog/batch_events.ts b/apps/sim/tools/posthog/batch_events.ts index 0eeea5d9b76..0e16fea5951 100644 --- a/apps/sim/tools/posthog/batch_events.ts +++ b/apps/sim/tools/posthog/batch_events.ts @@ -1,8 +1,11 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogIngestBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogBatchEventsParams { projectApiKey: string region?: 'us' | 'eu' + host?: string batch: string } @@ -35,6 +38,13 @@ export const batchEventsTool: ToolConfig { - const baseUrl = - params.region === 'eu' ? 'https://eu.i.posthog.com' : 'https://us.i.posthog.com' + const baseUrl = getPostHogIngestBaseUrl(params.region, params.host) return `${baseUrl}/batch/` }, method: 'POST', @@ -55,40 +64,36 @@ export const batchEventsTool: ToolConfig { - let batch: any[] + let batch: unknown try { batch = JSON.parse(params.batch) - } catch (e) { - batch = [] + } catch (error) { + throw new Error(`Invalid batch JSON: ${getErrorMessage(error)}`) + } + if (!Array.isArray(batch)) { + throw new Error('batch must be a JSON array of events') } return { api_key: params.projectApiKey, - batch: batch, + batch, } }, }, - transformResponse: async (response: Response) => { - if (response.ok) { - const data = await response.json() - return { - success: true, - output: { - status: 'Batch events captured successfully', - events_processed: data.status === 1 ? JSON.parse(data.batch || '[]').length : 0, - }, - } - } + transformResponse: async (response: Response, params) => { + const data = await response.json() + const eventsProcessed = params ? (JSON.parse(params.batch) as unknown[]).length : 0 + const success = data.status === 1 - const error = await response.text() return { - success: false, + success, output: { - status: 'Failed to capture batch events', - events_processed: 0, + status: success + ? 'Batch events captured successfully' + : `Batch events capture failed (status: ${data.status})`, + events_processed: success ? eventsProcessed : 0, }, - error: error || 'Unknown error occurred', } }, diff --git a/apps/sim/tools/posthog/capture_event.ts b/apps/sim/tools/posthog/capture_event.ts index aa0ad4c2a9d..154f89c5e41 100644 --- a/apps/sim/tools/posthog/capture_event.ts +++ b/apps/sim/tools/posthog/capture_event.ts @@ -1,8 +1,11 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogIngestBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogCaptureEventParams { projectApiKey: string region?: 'us' | 'eu' + host?: string event: string distinctId: string properties?: string @@ -38,6 +41,13 @@ export const captureEventTool: ToolConfig { - const baseUrl = - params.region === 'eu' ? 'https://eu.i.posthog.com' : 'https://us.i.posthog.com' + const baseUrl = getPostHogIngestBaseUrl(params.region, params.host) return `${baseUrl}/capture/` }, method: 'POST', @@ -87,8 +96,8 @@ export const captureEventTool: ToolConfig { - if (response.ok) { - return { - success: true, - output: { - status: 'Event captured successfully', - }, - } - } + const data = await response.json() + const success = data.status === 1 - const error = await response.text() return { - success: false, + success, output: { - status: 'Failed to capture event', + status: success + ? 'Event captured successfully' + : `Event capture failed (status: ${data.status})`, }, - error: error || 'Unknown error occurred', } }, diff --git a/apps/sim/tools/posthog/create_annotation.ts b/apps/sim/tools/posthog/create_annotation.ts index f60dc975a01..788cd4880de 100644 --- a/apps/sim/tools/posthog/create_annotation.ts +++ b/apps/sim/tools/posthog/create_annotation.ts @@ -1,14 +1,16 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogCreateAnnotationParams { apiKey: string projectId: string region: string + host?: string content: string date_marker: string scope?: string dashboard_item?: string - insight_short_id?: string + dashboard_id?: string } interface PostHogCreateAnnotationResponse { @@ -21,6 +23,7 @@ interface PostHogCreateAnnotationResponse { updated_at: string created_by: Record | null dashboard_item: number | null + dashboard_id: number | null insight_short_id: string | null insight_name: string | null scope: string @@ -37,6 +40,7 @@ export const createAnnotationTool: ToolConfig< description: 'Create a new annotation in PostHog. Mark important events on your graphs with date and description.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -58,6 +62,13 @@ export const createAnnotationTool: ToolConfig< description: 'PostHog cloud region: "us" or "eu" (default: "us")', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, content: { type: 'string', required: true, @@ -75,25 +86,28 @@ export const createAnnotationTool: ToolConfig< type: 'string', required: false, visibility: 'user-or-llm', - description: 'Scope of the annotation: "project" or "dashboard_item" (default: "project")', + description: + 'Scope of the annotation: "project", "organization", "dashboard", or "dashboard_item" (default: "project")', }, dashboard_item: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'ID of dashboard item to attach this annotation to', + description: + 'ID of the dashboard tile (insight) to attach this annotation to (used when scope is "dashboard_item")', }, - insight_short_id: { + dashboard_id: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'Short ID of the insight to attach this annotation to', + description: + 'ID of the dashboard to attach this annotation to (used when scope is "dashboard")', }, }, request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/annotations/` }, method: 'POST', @@ -115,8 +129,8 @@ export const createAnnotationTool: ToolConfig< body.dashboard_item = Number(params.dashboard_item) } - if (params.insight_short_id) { - body.insight_short_id = params.insight_short_id + if (params.dashboard_id) { + body.dashboard_id = Number(params.dashboard_id) } return body @@ -136,6 +150,7 @@ export const createAnnotationTool: ToolConfig< updated_at: data.updated_at, created_by: data.created_by || null, dashboard_item: data.dashboard_item || null, + dashboard_id: data.dashboard_id || null, insight_short_id: data.insight_short_id || null, insight_name: data.insight_name || null, scope: data.scope || '', @@ -175,6 +190,11 @@ export const createAnnotationTool: ToolConfig< description: 'ID of dashboard item this annotation is attached to', optional: true, }, + dashboard_id: { + type: 'number', + description: 'ID of the dashboard this annotation is attached to', + optional: true, + }, insight_short_id: { type: 'string', description: 'Short ID of the insight this annotation is attached to', @@ -187,7 +207,7 @@ export const createAnnotationTool: ToolConfig< }, scope: { type: 'string', - description: 'Scope of the annotation (project or dashboard_item)', + description: 'Scope of the annotation (project, organization, dashboard, or dashboard_item)', }, deleted: { type: 'boolean', diff --git a/apps/sim/tools/posthog/create_cohort.ts b/apps/sim/tools/posthog/create_cohort.ts index 71cc283e808..25ebdf8fc05 100644 --- a/apps/sim/tools/posthog/create_cohort.ts +++ b/apps/sim/tools/posthog/create_cohort.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogCreateCohortParams { apiKey: string projectId: string region: string + host?: string name: string description?: string filters?: string @@ -38,6 +40,7 @@ export const createCohortTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/cohorts/` }, method: 'POST', diff --git a/apps/sim/tools/posthog/create_dashboard.ts b/apps/sim/tools/posthog/create_dashboard.ts new file mode 100644 index 00000000000..4cfa617fd63 --- /dev/null +++ b/apps/sim/tools/posthog/create_dashboard.ts @@ -0,0 +1,183 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' +import type { ToolConfig } from '@/tools/types' + +interface PostHogCreateDashboardParams { + apiKey: string + projectId: string + region?: 'us' | 'eu' + host?: string + name: string + description?: string + pinned?: boolean + tags?: string + useTemplate?: string +} + +interface PostHogCreateDashboardResponse { + success: boolean + output: { + id: number + name: string + description: string + pinned: boolean + created_at: string + tiles: Array> + filters: Record + tags: string[] + } +} + +export const createDashboardTool: ToolConfig< + PostHogCreateDashboardParams, + PostHogCreateDashboardResponse +> = { + id: 'posthog_create_dashboard', + name: 'PostHog Create Dashboard', + description: + 'Create a new dashboard in PostHog. Optionally seed it from a built-in template, then attach insights to it afterward.', + version: '1.0.0', + errorExtractor: 'posthog-errors', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PostHog Personal API Key', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The PostHog project ID (e.g., "12345" or project UUID)', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'PostHog cloud region: "us" or "eu" (default: "us")', + default: 'us', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name for the new dashboard', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the dashboard', + }, + pinned: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to pin the dashboard to the sidebar', + }, + tags: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of tags for the dashboard', + }, + useTemplate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Name of a built-in PostHog dashboard template to seed this dashboard from (e.g., "Product analytics")', + }, + }, + + request: { + url: (params) => { + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/dashboards/` + }, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const body: Record = { + name: params.name, + } + + if (params.description) body.description = params.description + if (params.pinned !== undefined) body.pinned = params.pinned + if (params.useTemplate) body.use_template = params.useTemplate + + if (params.tags) { + body.tags = params.tags + .split(',') + .map((tag: string) => tag.trim()) + .filter((tag: string) => tag.length > 0) + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name || '', + description: data.description || '', + pinned: data.pinned || false, + created_at: data.created_at, + tiles: data.tiles || [], + filters: data.filters || {}, + tags: data.tags || [], + }, + } + }, + + outputs: { + id: { + type: 'number', + description: 'Unique identifier for the created dashboard', + }, + name: { + type: 'string', + description: 'Name of the dashboard', + }, + description: { + type: 'string', + description: 'Description of the dashboard', + }, + pinned: { + type: 'boolean', + description: 'Whether the dashboard is pinned', + }, + created_at: { + type: 'string', + description: 'ISO timestamp when dashboard was created', + }, + tiles: { + type: 'array', + description: 'Tiles/widgets on the dashboard', + }, + filters: { + type: 'object', + description: 'Global filters applied to the dashboard', + }, + tags: { + type: 'array', + description: 'Tags associated with the dashboard', + }, + }, +} diff --git a/apps/sim/tools/posthog/create_experiment.ts b/apps/sim/tools/posthog/create_experiment.ts index 2c9ef15ca55..49cc494d39d 100644 --- a/apps/sim/tools/posthog/create_experiment.ts +++ b/apps/sim/tools/posthog/create_experiment.ts @@ -1,15 +1,17 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface CreateExperimentParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string name: string description?: string featureFlagKey: string parameters?: string filters?: string - variants?: string startDate?: string endDate?: string } @@ -22,7 +24,6 @@ interface Experiment { feature_flag: Record parameters: Record filters: Record - variants: Record start_date: string | null end_date: string | null created_at: string @@ -39,6 +40,7 @@ export const createExperimentTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/experiments/` }, method: 'POST', @@ -132,24 +135,16 @@ export const createExperimentTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/feature_flags/` }, method: 'POST', @@ -115,8 +126,8 @@ export const createFeatureFlagTool: ToolConfig query: Record | null created_at: string created_by: Record | null last_modified_at: string - saved: boolean dashboards: number[] tags: string[] } @@ -35,9 +34,9 @@ export const createInsightTool: ToolConfig< > = { id: 'posthog_create_insight', name: 'PostHog Create Insight', - description: - 'Create a new insight in PostHog. Requires insight name and configuration filters or query.', + description: 'Create a new insight in PostHog. Requires insight name and a query configuration.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -59,6 +58,13 @@ export const createInsightTool: ToolConfig< description: 'PostHog cloud region: "us" or "eu" (default: "us")', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, name: { type: 'string', required: false, @@ -72,12 +78,6 @@ export const createInsightTool: ToolConfig< visibility: 'user-or-llm', description: 'Description of the insight', }, - filters: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'JSON string of filter configuration for the insight', - }, query: { type: 'string', required: false, @@ -100,7 +100,7 @@ export const createInsightTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/insights/` }, method: 'POST', @@ -117,14 +117,6 @@ export const createInsightTool: ToolConfig< body.description = params.description } - if (params.filters) { - try { - body.filters = JSON.parse(params.filters) - } catch (e) { - body.filters = {} - } - } - if (params.query) { try { body.query = JSON.parse(params.query) @@ -160,12 +152,10 @@ export const createInsightTool: ToolConfig< id: data.id, name: data.name || '', description: data.description || '', - filters: data.filters || {}, query: data.query || null, created_at: data.created_at, created_by: data.created_by || null, last_modified_at: data.last_modified_at, - saved: data.saved || false, dashboards: data.dashboards || [], tags: data.tags || [], }, @@ -185,10 +175,6 @@ export const createInsightTool: ToolConfig< type: 'string', description: 'Description of the insight', }, - filters: { - type: 'object', - description: 'Filter configuration for the insight', - }, query: { type: 'object', description: 'Query configuration for the insight', @@ -207,10 +193,6 @@ export const createInsightTool: ToolConfig< type: 'string', description: 'ISO timestamp when insight was last modified', }, - saved: { - type: 'boolean', - description: 'Whether the insight is saved', - }, dashboards: { type: 'array', description: 'IDs of dashboards this insight appears on', diff --git a/apps/sim/tools/posthog/create_survey.ts b/apps/sim/tools/posthog/create_survey.ts index f5155df2807..13fdc1568f8 100644 --- a/apps/sim/tools/posthog/create_survey.ts +++ b/apps/sim/tools/posthog/create_survey.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogSurveyQuestion { @@ -15,6 +16,7 @@ interface PostHogCreateSurveyParams { apiKey: string projectId: string region?: 'us' | 'eu' + host?: string name: string description?: string type?: 'popover' | 'api' @@ -60,6 +62,7 @@ export const createSurveyTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/surveys/` }, method: 'POST', diff --git a/apps/sim/tools/posthog/delete_feature_flag.ts b/apps/sim/tools/posthog/delete_feature_flag.ts index 9e2df9110a2..8b33a90bf23 100644 --- a/apps/sim/tools/posthog/delete_feature_flag.ts +++ b/apps/sim/tools/posthog/delete_feature_flag.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface DeleteFeatureFlagParams { projectId: string flagId: string region: 'us' | 'eu' + host?: string apiKey: string } @@ -18,6 +20,7 @@ export const deleteFeatureFlagTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}/` }, - method: 'DELETE', + // PostHog does not allow a hard DELETE on feature flags (always returns 405). + // Deletion is a soft-delete via PATCH with deleted: true. + method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, 'Content-Type': 'application/json', }), + body: () => ({ deleted: true }), }, - transformResponse: async (response: Response) => { + transformResponse: async () => { return { success: true, message: 'Feature flag deleted successfully', diff --git a/apps/sim/tools/posthog/delete_person.ts b/apps/sim/tools/posthog/delete_person.ts index ccfce302282..262b9954f75 100644 --- a/apps/sim/tools/posthog/delete_person.ts +++ b/apps/sim/tools/posthog/delete_person.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogDeletePersonParams { apiKey: string region?: 'us' | 'eu' + host?: string projectId: string personId: string } @@ -21,6 +23,7 @@ export const deletePersonTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' - return `${baseUrl}/api/projects/${params.projectId}/persons/${params.personId}/` + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/persons/bulk_delete/` }, - method: 'DELETE', + method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, 'Content-Type': 'application/json', }), + body: (params) => ({ ids: [params.personId] }), }, transformResponse: async (response: Response) => { - if (response.ok || response.status === 204) { - return { - success: true, - output: { - status: 'Person deleted successfully', - }, - } - } - - const error = await response.text() + const data = await response.json() + const success = data.persons_deleted > 0 return { - success: false, + success, output: { - status: 'Failed to delete person', + status: success ? 'Person deleted successfully' : 'No matching person found to delete', }, - error: error || 'Unknown error occurred', } }, diff --git a/apps/sim/tools/posthog/delete_survey.ts b/apps/sim/tools/posthog/delete_survey.ts new file mode 100644 index 00000000000..293f22035d7 --- /dev/null +++ b/apps/sim/tools/posthog/delete_survey.ts @@ -0,0 +1,89 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' +import type { ToolConfig } from '@/tools/types' + +interface PostHogDeleteSurveyParams { + apiKey: string + projectId: string + surveyId: string + region?: 'us' | 'eu' + host?: string +} + +interface PostHogDeleteSurveyResponse { + success: boolean + output: { + status: string + } +} + +export const deleteSurveyTool: ToolConfig = + { + id: 'posthog_delete_survey', + name: 'PostHog Delete Survey', + description: 'Delete a survey from PostHog. Use this to remove expired or unused surveys.', + version: '1.0.0', + errorExtractor: 'posthog-errors', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PostHog Personal API Key', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'PostHog Project ID (e.g., "12345" or project UUID)', + }, + surveyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Survey ID to delete (e.g., "01234567-89ab-cdef-0123-456789abcdef")', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'PostHog cloud region: us or eu (default: us)', + default: 'us', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, + }, + + request: { + url: (params) => { + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/surveys/${params.surveyId}/` + }, + method: 'DELETE', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + status: 'Survey deleted successfully', + }, + } + }, + + outputs: { + status: { + type: 'string', + description: 'Status message indicating whether the survey was deleted successfully', + }, + }, + } diff --git a/apps/sim/tools/posthog/evaluate_flags.ts b/apps/sim/tools/posthog/evaluate_flags.ts index ae157cc2821..ca8235ad0cc 100644 --- a/apps/sim/tools/posthog/evaluate_flags.ts +++ b/apps/sim/tools/posthog/evaluate_flags.ts @@ -1,7 +1,10 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogIngestBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface EvaluateFlagsParams { region: 'us' | 'eu' + host?: string projectApiKey: string distinctId: string groups?: string @@ -33,6 +36,13 @@ export const evaluateFlagsTool: ToolConfig { - const baseUrl = - params.region === 'eu' ? 'https://eu.i.posthog.com' : 'https://us.i.posthog.com' - return `${baseUrl}/decide?v=3` + const baseUrl = getPostHogIngestBaseUrl(params.region, params.host) + return `${baseUrl}/flags/?v=2` }, method: 'POST', - headers: (params) => ({ - Authorization: `Bearer ${params.projectApiKey}`, + headers: () => ({ 'Content-Type': 'application/json', }), body: (params) => { const body: Record = { + api_key: params.projectApiKey, distinct_id: params.distinctId, } if (params.groups) { try { body.groups = JSON.parse(params.groups) - } catch { - body.groups = {} + } catch (error) { + throw new Error(`Invalid groups JSON: ${getErrorMessage(error)}`) } } if (params.personProperties) { try { body.person_properties = JSON.parse(params.personProperties) - } catch { - body.person_properties = {} + } catch (error) { + throw new Error(`Invalid personProperties JSON: ${getErrorMessage(error)}`) } } if (params.groupProperties) { try { body.group_properties = JSON.parse(params.groupProperties) - } catch { - body.group_properties = {} + } catch (error) { + throw new Error(`Invalid groupProperties JSON: ${getErrorMessage(error)}`) } } @@ -111,10 +120,28 @@ export const evaluateFlagsTool: ToolConfig { const data = await response.json() + const flags: Record< + string, + { enabled?: boolean; variant?: string; metadata?: { payload?: string } } + > = data.flags || {} + + const feature_flags: FlagEvaluation = {} + const feature_flag_payloads: Record = {} + + for (const [key, flag] of Object.entries(flags)) { + feature_flags[key] = flag.variant ?? flag.enabled ?? false + if (flag.metadata?.payload !== undefined) { + try { + feature_flag_payloads[key] = JSON.parse(flag.metadata.payload) + } catch { + feature_flag_payloads[key] = flag.metadata.payload + } + } + } return { - feature_flags: data.featureFlags || {}, - feature_flag_payloads: data.featureFlagPayloads || {}, + feature_flags, + feature_flag_payloads, errors_while_computing_flags: data.errorsWhileComputingFlags || false, } }, diff --git a/apps/sim/tools/posthog/get_cohort.ts b/apps/sim/tools/posthog/get_cohort.ts index eeddfd78bb9..f4887544aba 100644 --- a/apps/sim/tools/posthog/get_cohort.ts +++ b/apps/sim/tools/posthog/get_cohort.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetCohortParams { @@ -5,6 +6,7 @@ interface PostHogGetCohortParams { projectId: string cohortId: string region: string + host?: string } interface PostHogGetCohortResponse { @@ -34,6 +36,7 @@ export const getCohortTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/cohorts/${params.cohortId}/` }, method: 'GET', diff --git a/apps/sim/tools/posthog/get_dashboard.ts b/apps/sim/tools/posthog/get_dashboard.ts index eabeb33f0ed..4ba2cb33168 100644 --- a/apps/sim/tools/posthog/get_dashboard.ts +++ b/apps/sim/tools/posthog/get_dashboard.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetDashboardParams { @@ -5,6 +6,7 @@ interface PostHogGetDashboardParams { projectId: string dashboardId: string region: string + host?: string } interface PostHogGetDashboardResponse { @@ -32,6 +34,7 @@ export const getDashboardTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/dashboards/${params.dashboardId}/` }, method: 'GET', diff --git a/apps/sim/tools/posthog/get_event_definition.ts b/apps/sim/tools/posthog/get_event_definition.ts index 323ab20fb00..ee1e0bee0e8 100644 --- a/apps/sim/tools/posthog/get_event_definition.ts +++ b/apps/sim/tools/posthog/get_event_definition.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetEventDefinitionParams { projectId: string eventDefinitionId: string region: 'us' | 'eu' + host?: string apiKey: string } @@ -12,8 +14,6 @@ interface EventDefinition { name: string description: string tags: string[] - volume_30_day: number | null - query_usage_30_day: number | null created_at: string last_seen_at: string | null updated_at: string @@ -36,6 +36,7 @@ export const getEventDefinitionTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/event_definitions/${params.eventDefinitionId}` }, method: 'GET', @@ -84,8 +92,6 @@ export const getEventDefinitionTool: ToolConfig parameters: Record filters: Record - variants: Record start_date: string | null end_date: string | null created_at: string @@ -34,6 +35,7 @@ export const getExperimentTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' - return `${baseUrl}/api/projects/${params.projectId}/experiments/${params.experimentId}` + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/experiments/${params.experimentId}/` }, method: 'GET', headers: (params) => ({ @@ -94,7 +103,6 @@ export const getExperimentTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}/` }, method: 'GET', headers: (params) => ({ diff --git a/apps/sim/tools/posthog/get_insight.ts b/apps/sim/tools/posthog/get_insight.ts index 89299ba345f..cd52a54d020 100644 --- a/apps/sim/tools/posthog/get_insight.ts +++ b/apps/sim/tools/posthog/get_insight.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetInsightParams { @@ -5,6 +6,7 @@ interface PostHogGetInsightParams { projectId: string insightId: string region: string + host?: string } interface PostHogGetInsightResponse { @@ -13,13 +15,11 @@ interface PostHogGetInsightResponse { id: number name: string description: string - filters: Record query: Record | null created_at: string created_by: Record | null last_modified_at: string last_modified_by: Record | null - saved: boolean dashboards: number[] tags: string[] favorited: boolean @@ -30,8 +30,9 @@ export const getInsightTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) return `${baseUrl}/api/projects/${params.projectId}/insights/${params.insightId}/` }, method: 'GET', @@ -82,13 +90,11 @@ export const getInsightTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/organizations/${params.organizationId}/` }, method: 'GET', diff --git a/apps/sim/tools/posthog/get_person.ts b/apps/sim/tools/posthog/get_person.ts index ca3b53be7cf..5b022377ebf 100644 --- a/apps/sim/tools/posthog/get_person.ts +++ b/apps/sim/tools/posthog/get_person.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogGetPersonParams { apiKey: string region?: 'us' | 'eu' + host?: string projectId: string personId: string } @@ -26,6 +28,7 @@ export const getPersonTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/persons/${params.personId}/` }, method: 'GET', @@ -68,24 +78,6 @@ export const getPersonTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - person: { - id: '', - name: '', - distinct_ids: [], - properties: {}, - created_at: '', - uuid: '', - }, - }, - error: error || 'Failed to get person', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_project.ts b/apps/sim/tools/posthog/get_project.ts index fdabb347a08..9c4d4bb9aba 100644 --- a/apps/sim/tools/posthog/get_project.ts +++ b/apps/sim/tools/posthog/get_project.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogGetProjectParams { projectId: string apiKey: string region?: 'us' | 'eu' + host?: string } interface PostHogProjectDetail { @@ -50,6 +52,7 @@ export const getProjectTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/` }, method: 'GET', diff --git a/apps/sim/tools/posthog/get_property_definition.ts b/apps/sim/tools/posthog/get_property_definition.ts index fe491340367..e1d8f822951 100644 --- a/apps/sim/tools/posthog/get_property_definition.ts +++ b/apps/sim/tools/posthog/get_property_definition.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetPropertyDefinitionParams { projectId: string propertyDefinitionId: string region: 'us' | 'eu' + host?: string apiKey: string } @@ -15,9 +17,7 @@ interface PropertyDefinition { is_numerical: boolean is_seen_on_filtered_events: boolean | null property_type: string - type: 'event' | 'person' | 'group' - volume_30_day: number | null - query_usage_30_day: number | null + type: 'event' | 'person' | 'group' | 'session' created_at: string updated_at: string updated_by: { @@ -30,7 +30,6 @@ interface PropertyDefinition { verified: boolean verified_at: string | null verified_by: string | null - example: string | null } export const getPropertyDefinitionTool: ToolConfig< @@ -42,6 +41,7 @@ export const getPropertyDefinitionTool: ToolConfig< description: 'Get details of a specific property definition in PostHog. Returns comprehensive information about the property including metadata, type, usage statistics, and verification status.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { projectId: { @@ -62,6 +62,13 @@ export const getPropertyDefinitionTool: ToolConfig< visibility: 'user-only', description: 'PostHog cloud region: us or eu', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, apiKey: { type: 'string', required: true, @@ -72,7 +79,7 @@ export const getPropertyDefinitionTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/property_definitions/${params.propertyDefinitionId}` }, method: 'GET', @@ -94,15 +101,12 @@ export const getPropertyDefinitionTool: ToolConfig< is_seen_on_filtered_events: data.is_seen_on_filtered_events ?? null, property_type: data.property_type, type: data.type, - volume_30_day: data.volume_30_day ?? null, - query_usage_30_day: data.query_usage_30_day ?? null, created_at: data.created_at, updated_at: data.updated_at, updated_by: data.updated_by ?? null, verified: data.verified || false, verified_at: data.verified_at ?? null, verified_by: data.verified_by ?? null, - example: data.example ?? null, } }, @@ -138,17 +142,7 @@ export const getPropertyDefinitionTool: ToolConfig< }, type: { type: 'string', - description: 'Property type: event, person, or group', - }, - volume_30_day: { - type: 'number', - description: 'Number of times property was seen in the last 30 days', - optional: true, - }, - query_usage_30_day: { - type: 'number', - description: 'Number of times this property was queried in the last 30 days', - optional: true, + description: 'Property type: event, person, group, or session', }, created_at: { type: 'string', @@ -177,10 +171,5 @@ export const getPropertyDefinitionTool: ToolConfig< description: 'User who verified the property', optional: true, }, - example: { - type: 'string', - description: 'Example value for the property', - optional: true, - }, }, } diff --git a/apps/sim/tools/posthog/get_session_recording.ts b/apps/sim/tools/posthog/get_session_recording.ts index 551bc01fdb6..831591fd11f 100644 --- a/apps/sim/tools/posthog/get_session_recording.ts +++ b/apps/sim/tools/posthog/get_session_recording.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetSessionRecordingParams { @@ -5,6 +6,7 @@ interface PostHogGetSessionRecordingParams { projectId: string recordingId: string region?: 'us' | 'eu' + host?: string } interface PostHogSessionRecording { @@ -27,13 +29,6 @@ interface PostHogSessionRecording { name?: string properties?: Record } - matching_events?: Array<{ - id: string - event: string - timestamp: string - properties: Record - }> - snapshot_data_by_window_id?: Record } interface PostHogGetSessionRecordingResponse { @@ -51,6 +46,7 @@ export const getSessionRecordingTool: ToolConfig< name: 'PostHog Get Session Recording', description: 'Get details of a specific session recording in PostHog by ID.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -79,11 +75,18 @@ export const getSessionRecordingTool: ToolConfig< description: 'PostHog cloud region: us or eu (default: us)', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, }, request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/session_recordings/${params.recordingId}/` }, method: 'GET', @@ -124,7 +127,6 @@ export const getSessionRecordingTool: ToolConfig< console_error_count: { type: 'number', description: 'Number of console errors' }, start_url: { type: 'string', description: 'Starting URL of the recording' }, person: { type: 'object', description: 'Person information' }, - matching_events: { type: 'array', description: 'Events that occurred during recording' }, }, }, }, diff --git a/apps/sim/tools/posthog/get_survey.ts b/apps/sim/tools/posthog/get_survey.ts index 04c31bce284..9331e634d6f 100644 --- a/apps/sim/tools/posthog/get_survey.ts +++ b/apps/sim/tools/posthog/get_survey.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogGetSurveyParams { @@ -5,6 +6,7 @@ interface PostHogGetSurveyParams { projectId: string surveyId: string region?: 'us' | 'eu' + host?: string } interface PostHogSurveyQuestion { @@ -48,6 +50,7 @@ export const getSurveyTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/surveys/${params.surveyId}/` }, method: 'GET', diff --git a/apps/sim/tools/posthog/index.ts b/apps/sim/tools/posthog/index.ts index 7868ecab553..76d30913838 100644 --- a/apps/sim/tools/posthog/index.ts +++ b/apps/sim/tools/posthog/index.ts @@ -4,12 +4,14 @@ import { batchEventsTool } from '@/tools/posthog/batch_events' import { captureEventTool } from '@/tools/posthog/capture_event' import { createAnnotationTool } from '@/tools/posthog/create_annotation' import { createCohortTool } from '@/tools/posthog/create_cohort' +import { createDashboardTool } from '@/tools/posthog/create_dashboard' import { createExperimentTool } from '@/tools/posthog/create_experiment' import { createFeatureFlagTool } from '@/tools/posthog/create_feature_flag' import { createInsightTool } from '@/tools/posthog/create_insight' import { createSurveyTool } from '@/tools/posthog/create_survey' import { deleteFeatureFlagTool } from '@/tools/posthog/delete_feature_flag' import { deletePersonTool } from '@/tools/posthog/delete_person' +import { deleteSurveyTool } from '@/tools/posthog/delete_survey' import { evaluateFlagsTool } from '@/tools/posthog/evaluate_flags' import { getCohortTool } from '@/tools/posthog/get_cohort' import { getDashboardTool } from '@/tools/posthog/get_dashboard' @@ -44,8 +46,11 @@ import { listSessionRecordingsTool } from '@/tools/posthog/list_session_recordin // Engagement import { listSurveysTool } from '@/tools/posthog/list_surveys' import { queryTool } from '@/tools/posthog/query' +import { updateCohortTool } from '@/tools/posthog/update_cohort' import { updateEventDefinitionTool } from '@/tools/posthog/update_event_definition' +import { updateExperimentTool } from '@/tools/posthog/update_experiment' import { updateFeatureFlagTool } from '@/tools/posthog/update_feature_flag' +import { updateInsightTool } from '@/tools/posthog/update_insight' import { updatePropertyDefinitionTool } from '@/tools/posthog/update_property_definition' import { updateSurveyTool } from '@/tools/posthog/update_survey' @@ -60,12 +65,15 @@ export const posthogQueryTool = queryTool export const posthogListInsightsTool = listInsightsTool export const posthogGetInsightTool = getInsightTool export const posthogCreateInsightTool = createInsightTool +export const posthogUpdateInsightTool = updateInsightTool export const posthogListDashboardsTool = listDashboardsTool export const posthogGetDashboardTool = getDashboardTool +export const posthogCreateDashboardTool = createDashboardTool export const posthogListActionsTool = listActionsTool export const posthogListCohortsTool = listCohortsTool export const posthogGetCohortTool = getCohortTool export const posthogCreateCohortTool = createCohortTool +export const posthogUpdateCohortTool = updateCohortTool export const posthogListAnnotationsTool = listAnnotationsTool export const posthogCreateAnnotationTool = createAnnotationTool @@ -78,11 +86,13 @@ export const posthogEvaluateFlagsTool = evaluateFlagsTool export const posthogListExperimentsTool = listExperimentsTool export const posthogGetExperimentTool = getExperimentTool export const posthogCreateExperimentTool = createExperimentTool +export const posthogUpdateExperimentTool = updateExperimentTool export const posthogListSurveysTool = listSurveysTool export const posthogGetSurveyTool = getSurveyTool export const posthogCreateSurveyTool = createSurveyTool export const posthogUpdateSurveyTool = updateSurveyTool +export const posthogDeleteSurveyTool = deleteSurveyTool export const posthogListSessionRecordingsTool = listSessionRecordingsTool export const posthogGetSessionRecordingTool = getSessionRecordingTool export const posthogListRecordingPlaylistsTool = listRecordingPlaylistsTool diff --git a/apps/sim/tools/posthog/list_actions.ts b/apps/sim/tools/posthog/list_actions.ts index 263a5022883..1bd7a92fdc6 100644 --- a/apps/sim/tools/posthog/list_actions.ts +++ b/apps/sim/tools/posthog/list_actions.ts @@ -1,11 +1,14 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListActionsParams { apiKey: string projectId: string region: string + host?: string limit?: number offset?: number + search?: string } interface PostHogListActionsResponse { @@ -37,6 +40,7 @@ export const listActionsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) let url = `${baseUrl}/api/projects/${params.projectId}/actions/` const queryParams = [] if (params.limit) queryParams.push(`limit=${params.limit}`) if (params.offset) queryParams.push(`offset=${params.offset}`) + if (params.search) queryParams.push(`search=${encodeURIComponent(params.search)}`) if (queryParams.length > 0) { url += `?${queryParams.join('&')}` diff --git a/apps/sim/tools/posthog/list_annotations.ts b/apps/sim/tools/posthog/list_annotations.ts index a6e56140005..cd3602a4647 100644 --- a/apps/sim/tools/posthog/list_annotations.ts +++ b/apps/sim/tools/posthog/list_annotations.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListAnnotationsParams { apiKey: string projectId: string region: string + host?: string limit?: number offset?: number } @@ -22,6 +24,7 @@ interface PostHogListAnnotationsResponse { updated_at: string created_by: Record | null dashboard_item: number | null + dashboard_id: number | null insight_short_id: string | null insight_name: string | null scope: string @@ -39,6 +42,7 @@ export const listAnnotationsTool: ToolConfig< description: 'List all annotations in a PostHog project. Returns annotation content, timestamps, and associated insights.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -60,6 +64,13 @@ export const listAnnotationsTool: ToolConfig< description: 'PostHog cloud region: "us" or "eu" (default: "us")', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, limit: { type: 'number', required: false, @@ -76,7 +87,7 @@ export const listAnnotationsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) let url = `${baseUrl}/api/projects/${params.projectId}/annotations/` const queryParams = [] @@ -113,6 +124,7 @@ export const listAnnotationsTool: ToolConfig< updated_at: annotation.updated_at, created_by: annotation.created_by || null, dashboard_item: annotation.dashboard_item || null, + dashboard_id: annotation.dashboard_id || null, insight_short_id: annotation.insight_short_id || null, insight_name: annotation.insight_name || null, scope: annotation.scope || '', @@ -162,6 +174,10 @@ export const listAnnotationsTool: ToolConfig< type: 'number', description: 'ID of dashboard item this annotation is attached to', }, + dashboard_id: { + type: 'number', + description: 'ID of the dashboard this annotation is attached to', + }, insight_short_id: { type: 'string', description: 'Short ID of the insight this annotation is attached to', diff --git a/apps/sim/tools/posthog/list_cohorts.ts b/apps/sim/tools/posthog/list_cohorts.ts index 2c310844d88..d7be4511de8 100644 --- a/apps/sim/tools/posthog/list_cohorts.ts +++ b/apps/sim/tools/posthog/list_cohorts.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListCohortsParams { apiKey: string projectId: string region: string + host?: string limit?: number offset?: number } @@ -39,6 +41,7 @@ export const listCohortsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) let url = `${baseUrl}/api/projects/${params.projectId}/cohorts/` const queryParams = [] diff --git a/apps/sim/tools/posthog/list_dashboards.ts b/apps/sim/tools/posthog/list_dashboards.ts index 7cbb2b231ac..c19ffbe2cd0 100644 --- a/apps/sim/tools/posthog/list_dashboards.ts +++ b/apps/sim/tools/posthog/list_dashboards.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListDashboardsParams { apiKey: string projectId: string region: string + host?: string limit?: number offset?: number } @@ -39,6 +41,7 @@ export const listDashboardsTool: ToolConfig< description: 'List all dashboards in a PostHog project. Returns dashboard configurations, tiles, and metadata.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -60,6 +63,13 @@ export const listDashboardsTool: ToolConfig< description: 'PostHog cloud region: "us" or "eu" (default: "us")', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, limit: { type: 'number', required: false, @@ -76,7 +86,7 @@ export const listDashboardsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) let url = `${baseUrl}/api/projects/${params.projectId}/dashboards/` const queryParams = [] diff --git a/apps/sim/tools/posthog/list_event_definitions.ts b/apps/sim/tools/posthog/list_event_definitions.ts index 082f66d6db5..ecf5d26052b 100644 --- a/apps/sim/tools/posthog/list_event_definitions.ts +++ b/apps/sim/tools/posthog/list_event_definitions.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListEventDefinitionsParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string limit?: number offset?: number @@ -14,8 +16,6 @@ interface EventDefinition { name: string description: string tags: string[] - volume_30_day: number | null - query_usage_30_day: number | null created_at: string last_seen_at: string | null updated_at: string @@ -44,6 +44,7 @@ export const listEventDefinitionsTool: ToolConfig< description: 'List all event definitions in a PostHog project. Event definitions represent tracked events with metadata like descriptions, tags, and usage statistics.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { projectId: { @@ -58,6 +59,13 @@ export const listEventDefinitionsTool: ToolConfig< visibility: 'user-only', description: 'PostHog cloud region: us or eu', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, apiKey: { type: 'string', required: true, @@ -86,7 +94,7 @@ export const listEventDefinitionsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const queryParams = new URLSearchParams() if (params.limit) queryParams.append('limit', params.limit.toString()) @@ -115,8 +123,6 @@ export const listEventDefinitionsTool: ToolConfig< name: event.name, description: event.description || '', tags: event.tags || [], - volume_30_day: event.volume_30_day ?? null, - query_usage_30_day: event.query_usage_30_day ?? null, created_at: event.created_at, last_seen_at: event.last_seen_at ?? null, updated_at: event.updated_at, @@ -150,16 +156,6 @@ export const listEventDefinitionsTool: ToolConfig< name: { type: 'string', description: 'Event name' }, description: { type: 'string', description: 'Event description' }, tags: { type: 'array', description: 'Tags associated with the event' }, - volume_30_day: { - type: 'number', - description: 'Number of events received in the last 30 days', - optional: true, - }, - query_usage_30_day: { - type: 'number', - description: 'Number of times this event was queried in the last 30 days', - optional: true, - }, created_at: { type: 'string', description: 'ISO timestamp when the event was created' }, last_seen_at: { type: 'string', diff --git a/apps/sim/tools/posthog/list_experiments.ts b/apps/sim/tools/posthog/list_experiments.ts index a78c359652c..918ab47c7e7 100644 --- a/apps/sim/tools/posthog/list_experiments.ts +++ b/apps/sim/tools/posthog/list_experiments.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface ListExperimentsParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string limit?: number offset?: number @@ -16,7 +18,6 @@ interface Experiment { feature_flag: Record parameters: Record filters: Record - variants: Record start_date: string | null end_date: string | null created_at: string @@ -36,6 +37,7 @@ export const listExperimentsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL(`${baseUrl}/api/projects/${params.projectId}/experiments/`) if (params.limit) url.searchParams.append('limit', String(params.limit)) @@ -112,7 +121,6 @@ export const listExperimentsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL(`${baseUrl}/api/projects/${params.projectId}/feature_flags/`) if (params.limit) url.searchParams.append('limit', String(params.limit)) diff --git a/apps/sim/tools/posthog/list_insights.ts b/apps/sim/tools/posthog/list_insights.ts index dca948d80e9..15aff43ae6a 100644 --- a/apps/sim/tools/posthog/list_insights.ts +++ b/apps/sim/tools/posthog/list_insights.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListInsightsParams { apiKey: string projectId: string region: string + host?: string limit?: number offset?: number } @@ -18,13 +20,11 @@ interface PostHogListInsightsResponse { id: number name: string description: string - filters: Record query: Record | null created_at: string created_by: Record | null last_modified_at: string last_modified_by: Record | null - saved: boolean dashboards: number[] }> } @@ -35,8 +35,9 @@ export const listInsightsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region as 'us' | 'eu' | undefined, params.host) let url = `${baseUrl}/api/projects/${params.projectId}/insights/` const queryParams = [] @@ -107,13 +115,11 @@ export const listInsightsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/organizations/` }, method: 'GET', @@ -75,7 +85,7 @@ export const listOrganizationsTool: ToolConfig< return { success: true, output: { - organizations: data.results.map((org: any) => ({ + organizations: (data.results || []).map((org: any) => ({ id: org.id, name: org.name, slug: org.slug, diff --git a/apps/sim/tools/posthog/list_persons.ts b/apps/sim/tools/posthog/list_persons.ts index ba11802492a..442bf5b9c99 100644 --- a/apps/sim/tools/posthog/list_persons.ts +++ b/apps/sim/tools/posthog/list_persons.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogListPersonsParams { apiKey: string region?: 'us' | 'eu' + host?: string projectId: string limit?: number offset?: number @@ -33,6 +35,7 @@ export const listPersonsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL(`${baseUrl}/api/projects/${params.projectId}/persons/`) if (params.limit) url.searchParams.append('limit', params.limit.toString()) @@ -101,17 +111,6 @@ export const listPersonsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - persons: [], - }, - error: error || 'Failed to list persons', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_projects.ts b/apps/sim/tools/posthog/list_projects.ts index b3b227cc431..6c7493520b8 100644 --- a/apps/sim/tools/posthog/list_projects.ts +++ b/apps/sim/tools/posthog/list_projects.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogListProjectsParams { apiKey: string region?: 'us' | 'eu' + host?: string } interface PostHogProject { @@ -39,6 +41,7 @@ export const listProjectsTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/` }, method: 'GET', @@ -73,7 +83,7 @@ export const listProjectsTool: ToolConfig ({ + projects: (data.results || []).map((project: any) => ({ id: project.id, uuid: project.uuid, organization: project.organization, diff --git a/apps/sim/tools/posthog/list_property_definitions.ts b/apps/sim/tools/posthog/list_property_definitions.ts index 28e9eb47f57..f921c0efc9e 100644 --- a/apps/sim/tools/posthog/list_property_definitions.ts +++ b/apps/sim/tools/posthog/list_property_definitions.ts @@ -1,13 +1,15 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListPropertyDefinitionsParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string limit?: number offset?: number search?: string - type?: 'event' | 'person' | 'group' + type?: 'event' | 'person' | 'group' | 'session' } interface PropertyDefinition { @@ -18,9 +20,7 @@ interface PropertyDefinition { is_numerical: boolean is_seen_on_filtered_events: boolean | null property_type: string - type: 'event' | 'person' | 'group' - volume_30_day: number | null - query_usage_30_day: number | null + type: 'event' | 'person' | 'group' | 'session' created_at: string updated_at: string updated_by: { @@ -48,6 +48,7 @@ export const listPropertyDefinitionsTool: ToolConfig< description: 'List all property definitions in a PostHog project. Property definitions represent tracked properties with metadata like descriptions, tags, types, and usage statistics.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { projectId: { @@ -62,6 +63,13 @@ export const listPropertyDefinitionsTool: ToolConfig< visibility: 'user-only', description: 'PostHog cloud region: us or eu', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, apiKey: { type: 'string', required: true, @@ -96,7 +104,7 @@ export const listPropertyDefinitionsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const queryParams = new URLSearchParams() if (params.limit) queryParams.append('limit', params.limit.toString()) @@ -130,8 +138,6 @@ export const listPropertyDefinitionsTool: ToolConfig< is_seen_on_filtered_events: property.is_seen_on_filtered_events ?? null, property_type: property.property_type, type: property.type, - volume_30_day: property.volume_30_day ?? null, - query_usage_30_day: property.query_usage_30_day ?? null, created_at: property.created_at, updated_at: property.updated_at, updated_by: property.updated_by ?? null, @@ -171,17 +177,7 @@ export const listPropertyDefinitionsTool: ToolConfig< optional: true, }, property_type: { type: 'string', description: 'The data type of the property' }, - type: { type: 'string', description: 'Property type: event, person, or group' }, - volume_30_day: { - type: 'number', - description: 'Number of times property was seen in the last 30 days', - optional: true, - }, - query_usage_30_day: { - type: 'number', - description: 'Number of times this property was queried in the last 30 days', - optional: true, - }, + type: { type: 'string', description: 'Property type: event, person, group, or session' }, created_at: { type: 'string', description: 'ISO timestamp when the property was created', diff --git a/apps/sim/tools/posthog/list_recording_playlists.ts b/apps/sim/tools/posthog/list_recording_playlists.ts index dfb2561dbad..e1addcdb1ba 100644 --- a/apps/sim/tools/posthog/list_recording_playlists.ts +++ b/apps/sim/tools/posthog/list_recording_playlists.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListRecordingPlaylistsParams { apiKey: string projectId: string region?: 'us' | 'eu' + host?: string limit?: number offset?: number } @@ -47,6 +49,7 @@ export const listRecordingPlaylistsTool: ToolConfig< description: 'List session recording playlists in a PostHog project. Playlists allow you to organize and curate session recordings.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -68,6 +71,13 @@ export const listRecordingPlaylistsTool: ToolConfig< description: 'PostHog cloud region: us or eu (default: us)', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, limit: { type: 'number', required: false, @@ -84,7 +94,7 @@ export const listRecordingPlaylistsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL( `${baseUrl}/api/projects/${params.projectId}/session_recording_playlists/` ) diff --git a/apps/sim/tools/posthog/list_session_recordings.ts b/apps/sim/tools/posthog/list_session_recordings.ts index d0967e7c292..0e7eb433a1e 100644 --- a/apps/sim/tools/posthog/list_session_recordings.ts +++ b/apps/sim/tools/posthog/list_session_recordings.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListSessionRecordingsParams { apiKey: string projectId: string region?: 'us' | 'eu' + host?: string limit?: number offset?: number } @@ -48,6 +50,7 @@ export const listSessionRecordingsTool: ToolConfig< description: 'List session recordings in a PostHog project. Session recordings capture user interactions with your application.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -69,6 +72,13 @@ export const listSessionRecordingsTool: ToolConfig< description: 'PostHog cloud region: us or eu (default: us)', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, limit: { type: 'number', required: false, @@ -85,7 +95,7 @@ export const listSessionRecordingsTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL(`${baseUrl}/api/projects/${params.projectId}/session_recordings/`) if (params.limit) { diff --git a/apps/sim/tools/posthog/list_surveys.ts b/apps/sim/tools/posthog/list_surveys.ts index bb4d57cfaf7..4ea049f0b60 100644 --- a/apps/sim/tools/posthog/list_surveys.ts +++ b/apps/sim/tools/posthog/list_surveys.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogListSurveysParams { apiKey: string projectId: string region?: 'us' | 'eu' + host?: string limit?: number offset?: number } @@ -45,6 +47,7 @@ export const listSurveysTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) const url = new URL(`${baseUrl}/api/projects/${params.projectId}/surveys/`) if (params.limit) { diff --git a/apps/sim/tools/posthog/query.ts b/apps/sim/tools/posthog/query.ts index 050dd81f14e..ae9fed885f5 100644 --- a/apps/sim/tools/posthog/query.ts +++ b/apps/sim/tools/posthog/query.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogQueryParams { apiKey: string region?: 'us' | 'eu' + host?: string projectId: string query: string values?: string @@ -25,6 +27,7 @@ export const queryTool: ToolConfig = { description: "Execute a HogQL query in PostHog. HogQL is PostHog's SQL-like query language for analytics. Use this for advanced data retrieval and analysis.", version: '1.0.0', + errorExtractor: 'posthog-errors', params: { apiKey: { @@ -40,6 +43,13 @@ export const queryTool: ToolConfig = { description: 'PostHog region: us (default) or eu', default: 'us', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, projectId: { type: 'string', required: true, @@ -64,7 +74,7 @@ export const queryTool: ToolConfig = { request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/query/` }, method: 'POST', @@ -101,17 +111,6 @@ export const queryTool: ToolConfig = { }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - results: [], - }, - error: error || 'Failed to execute query', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/types.ts b/apps/sim/tools/posthog/types.ts index a7a9e046a8c..1afc97ade3a 100644 --- a/apps/sim/tools/posthog/types.ts +++ b/apps/sim/tools/posthog/types.ts @@ -158,8 +158,6 @@ interface PostHogEventDefinition { name: string description: string tags: string[] - volume_30_day?: number - query_usage_30_day?: number created_at: string last_seen_at?: string verified: boolean @@ -172,8 +170,6 @@ interface PostHogPropertyDefinition { tags: string[] is_numerical: boolean property_type: 'DateTime' | 'String' | 'Numeric' | 'Boolean' - volume_30_day?: number - query_usage_30_day?: number verified: boolean } diff --git a/apps/sim/tools/posthog/update_cohort.ts b/apps/sim/tools/posthog/update_cohort.ts new file mode 100644 index 00000000000..d2cd36b220f --- /dev/null +++ b/apps/sim/tools/posthog/update_cohort.ts @@ -0,0 +1,245 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' +import type { ToolConfig } from '@/tools/types' + +interface PostHogUpdateCohortParams { + apiKey: string + projectId: string + cohortId: string + region?: 'us' | 'eu' + host?: string + name?: string + description?: string + filters?: string + query?: string + isStatic?: boolean + groups?: string + deleted?: boolean +} + +interface PostHogUpdateCohortResponse { + success: boolean + output: { + id: number + name: string + description: string + groups: Array> + deleted: boolean + filters: Record + query: Record | null + created_at: string + is_calculating: boolean + count: number + is_static: boolean + version: number + } +} + +export const updateCohortTool: ToolConfig = + { + id: 'posthog_update_cohort', + name: 'PostHog Update Cohort', + description: + 'Update an existing cohort in PostHog. Can modify name, description, filters, query, static membership, and deleted status.', + version: '1.0.0', + errorExtractor: 'posthog-errors', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PostHog Personal API Key', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The PostHog project ID (e.g., "12345" or project UUID)', + }, + cohortId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The cohort ID to update (e.g., "42")', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'PostHog cloud region: "us" or "eu" (default: "us")', + default: 'us', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated name for the cohort', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated description of the cohort', + }, + filters: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of updated filter configuration for the cohort', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of updated query configuration for the cohort', + }, + isStatic: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the cohort is static', + }, + groups: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of updated groups that define the cohort', + }, + deleted: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Set to true to archive (soft-delete) the cohort', + }, + }, + + request: { + url: (params) => { + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/cohorts/${params.cohortId}/` + }, + method: 'PATCH', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const body: Record = {} + + if (params.name !== undefined) body.name = params.name + if (params.description !== undefined) body.description = params.description + + if (params.filters) { + try { + body.filters = JSON.parse(params.filters) + } catch (error) { + throw new Error(`Invalid filters JSON: ${getErrorMessage(error)}`) + } + } + + if (params.query) { + try { + body.query = JSON.parse(params.query) + } catch (error) { + throw new Error(`Invalid query JSON: ${getErrorMessage(error)}`) + } + } + + if (params.isStatic !== undefined) body.is_static = params.isStatic + + if (params.groups) { + try { + body.groups = JSON.parse(params.groups) + } catch (error) { + throw new Error(`Invalid groups JSON: ${getErrorMessage(error)}`) + } + } + + if (params.deleted !== undefined) body.deleted = params.deleted + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name || '', + description: data.description || '', + groups: data.groups || [], + deleted: data.deleted || false, + filters: data.filters || {}, + query: data.query || null, + created_at: data.created_at, + is_calculating: data.is_calculating || false, + count: data.count || 0, + is_static: data.is_static || false, + version: data.version || 0, + }, + } + }, + + outputs: { + id: { + type: 'number', + description: 'Unique identifier for the cohort', + }, + name: { + type: 'string', + description: 'Name of the cohort', + }, + description: { + type: 'string', + description: 'Description of the cohort', + }, + groups: { + type: 'array', + description: 'Groups that define the cohort', + }, + deleted: { + type: 'boolean', + description: 'Whether the cohort is deleted', + }, + filters: { + type: 'object', + description: 'Filter configuration for the cohort', + }, + query: { + type: 'object', + description: 'Query configuration for the cohort', + optional: true, + }, + created_at: { + type: 'string', + description: 'ISO timestamp when cohort was created', + }, + is_calculating: { + type: 'boolean', + description: 'Whether the cohort is being calculated', + }, + count: { + type: 'number', + description: 'Number of users in the cohort', + }, + is_static: { + type: 'boolean', + description: 'Whether the cohort is static', + }, + version: { + type: 'number', + description: 'Version number of the cohort', + }, + }, + } diff --git a/apps/sim/tools/posthog/update_event_definition.ts b/apps/sim/tools/posthog/update_event_definition.ts index 34ad2fe130d..3ca149cd0e6 100644 --- a/apps/sim/tools/posthog/update_event_definition.ts +++ b/apps/sim/tools/posthog/update_event_definition.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogUpdateEventDefinitionParams { projectId: string eventDefinitionId: string region: 'us' | 'eu' + host?: string apiKey: string description?: string tags?: string @@ -15,8 +17,6 @@ interface EventDefinition { name: string description: string tags: string[] - volume_30_day: number | null - query_usage_30_day: number | null created_at: string last_seen_at: string | null updated_at: string @@ -41,6 +41,7 @@ export const updateEventDefinitionTool: ToolConfig< description: 'Update an event definition in PostHog. Can modify description, tags, and verification status to maintain clean event schemas.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { projectId: { @@ -61,6 +62,13 @@ export const updateEventDefinitionTool: ToolConfig< visibility: 'user-only', description: 'PostHog cloud region: us or eu', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, apiKey: { type: 'string', required: true, @@ -89,7 +97,7 @@ export const updateEventDefinitionTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/event_definitions/${params.eventDefinitionId}` }, method: 'PATCH', @@ -127,8 +135,6 @@ export const updateEventDefinitionTool: ToolConfig< name: data.name, description: data.description || '', tags: data.tags || [], - volume_30_day: data.volume_30_day ?? null, - query_usage_30_day: data.query_usage_30_day ?? null, created_at: data.created_at, last_seen_at: data.last_seen_at ?? null, updated_at: data.updated_at, @@ -156,16 +162,6 @@ export const updateEventDefinitionTool: ToolConfig< type: 'array', description: 'Updated tags associated with the event', }, - volume_30_day: { - type: 'number', - description: 'Number of events received in the last 30 days', - optional: true, - }, - query_usage_30_day: { - type: 'number', - description: 'Number of times this event was queried in the last 30 days', - optional: true, - }, created_at: { type: 'string', description: 'ISO timestamp when the event was created', diff --git a/apps/sim/tools/posthog/update_experiment.ts b/apps/sim/tools/posthog/update_experiment.ts new file mode 100644 index 00000000000..b1090d1922f --- /dev/null +++ b/apps/sim/tools/posthog/update_experiment.ts @@ -0,0 +1,190 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' +import type { ToolConfig } from '@/tools/types' + +interface UpdateExperimentParams { + projectId: string + experimentId: string + region?: 'us' | 'eu' + host?: string + apiKey: string + name?: string + description?: string + parameters?: string + filters?: string + startDate?: string + endDate?: string + archived?: boolean +} + +interface Experiment { + id: number + name: string + description: string + feature_flag_key: string + feature_flag: Record + parameters: Record + filters: Record + start_date: string | null + end_date: string | null + created_at: string + archived: boolean +} + +interface UpdateExperimentResponse { + experiment: Experiment +} + +export const updateExperimentTool: ToolConfig = { + id: 'posthog_update_experiment', + name: 'PostHog Update Experiment', + description: + 'Update an existing experiment in PostHog. Use this to change dates, archive an experiment, or adjust its parameters and filters.', + version: '1.0.0', + errorExtractor: 'posthog-errors', + + params: { + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The PostHog project ID (e.g., "12345" or project UUID)', + }, + experimentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The experiment ID to update (e.g., "42")', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'PostHog cloud region: us or eu (default: us)', + default: 'us', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PostHog Personal API Key', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated experiment name', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated experiment description', + }, + parameters: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated experiment parameters as JSON string', + }, + filters: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated experiment filters as JSON string', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated start date (ISO 8601). Set this to launch a draft experiment.', + }, + endDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated end date (ISO 8601). Set this to conclude a running experiment.', + }, + archived: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to archive the experiment', + }, + }, + + request: { + url: (params) => { + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/experiments/${params.experimentId}/` + }, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + + if (params.name !== undefined) body.name = params.name + if (params.description !== undefined) body.description = params.description + + if (params.parameters) { + try { + body.parameters = JSON.parse(params.parameters) + } catch (error) { + throw new Error(`Invalid parameters JSON: ${getErrorMessage(error)}`) + } + } + + if (params.filters) { + try { + body.filters = JSON.parse(params.filters) + } catch (error) { + throw new Error(`Invalid filters JSON: ${getErrorMessage(error)}`) + } + } + + if (params.startDate !== undefined) body.start_date = params.startDate + if (params.endDate !== undefined) body.end_date = params.endDate + if (params.archived !== undefined) body.archived = params.archived + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + experiment: data, + } + }, + + outputs: { + experiment: { + type: 'object', + description: 'Updated experiment', + properties: { + id: { type: 'number', description: 'Experiment ID' }, + name: { type: 'string', description: 'Experiment name' }, + description: { type: 'string', description: 'Experiment description' }, + feature_flag_key: { type: 'string', description: 'Associated feature flag key' }, + feature_flag: { type: 'object', description: 'Feature flag details' }, + parameters: { type: 'object', description: 'Experiment parameters' }, + filters: { type: 'object', description: 'Experiment filters' }, + start_date: { type: 'string', description: 'Start date', optional: true }, + end_date: { type: 'string', description: 'End date', optional: true }, + created_at: { type: 'string', description: 'Creation timestamp' }, + archived: { type: 'boolean', description: 'Whether the experiment is archived' }, + }, + }, + }, +} diff --git a/apps/sim/tools/posthog/update_feature_flag.ts b/apps/sim/tools/posthog/update_feature_flag.ts index caef9db2c78..b806357d154 100644 --- a/apps/sim/tools/posthog/update_feature_flag.ts +++ b/apps/sim/tools/posthog/update_feature_flag.ts @@ -1,9 +1,12 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface UpdateFeatureFlagParams { projectId: string flagId: string region: 'us' | 'eu' + host?: string apiKey: string name?: string key?: string @@ -37,6 +40,7 @@ export const updateFeatureFlagTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}/` }, method: 'PATCH', headers: (params) => ({ @@ -125,8 +136,8 @@ export const updateFeatureFlagTool: ToolConfig | null + created_at: string + last_modified_at: string + dashboards: number[] + tags: string[] + favorited: boolean + } +} + +export const updateInsightTool: ToolConfig< + PostHogUpdateInsightParams, + PostHogUpdateInsightResponse +> = { + id: 'posthog_update_insight', + name: 'PostHog Update Insight', + description: + 'Update an existing insight in PostHog. Can modify name, description, query, dashboards, tags, and favorited status.', + version: '1.0.0', + errorExtractor: 'posthog-errors', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'PostHog Personal API Key', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The PostHog project ID (e.g., "12345" or project UUID)', + }, + insightId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The insight ID to update (e.g., "42" or short ID like "abc123")', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'PostHog cloud region: "us" or "eu" (default: "us")', + default: 'us', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated name for the insight', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated description for the insight', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of updated query configuration for the insight', + }, + dashboards: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of dashboard IDs to attach this insight to', + }, + tags: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of tags for the insight', + }, + favorited: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to mark the insight as favorited', + }, + }, + + request: { + url: (params) => { + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) + return `${baseUrl}/api/projects/${params.projectId}/insights/${params.insightId}/` + }, + method: 'PATCH', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const body: Record = {} + + if (params.name !== undefined) body.name = params.name + if (params.description !== undefined) body.description = params.description + + if (params.query) { + try { + body.query = JSON.parse(params.query) + } catch (error) { + throw new Error(`Invalid query JSON: ${getErrorMessage(error)}`) + } + } + + if (params.dashboards) { + body.dashboards = params.dashboards + .split(',') + .map((id: string) => Number(id.trim())) + .filter((id: number) => !Number.isNaN(id)) + } + + if (params.tags) { + body.tags = params.tags + .split(',') + .map((tag: string) => tag.trim()) + .filter((tag: string) => tag.length > 0) + } + + if (params.favorited !== undefined) body.favorited = params.favorited + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name || '', + description: data.description || '', + query: data.query || null, + created_at: data.created_at, + last_modified_at: data.last_modified_at, + dashboards: data.dashboards || [], + tags: data.tags || [], + favorited: data.favorited || false, + }, + } + }, + + outputs: { + id: { + type: 'number', + description: 'Unique identifier for the insight', + }, + name: { + type: 'string', + description: 'Name of the insight', + }, + description: { + type: 'string', + description: 'Description of the insight', + }, + query: { + type: 'object', + description: 'Query configuration for the insight', + optional: true, + }, + created_at: { + type: 'string', + description: 'ISO timestamp when insight was created', + }, + last_modified_at: { + type: 'string', + description: 'ISO timestamp when insight was last modified', + }, + dashboards: { + type: 'array', + description: 'IDs of dashboards this insight appears on', + }, + tags: { + type: 'array', + description: 'Tags associated with the insight', + }, + favorited: { + type: 'boolean', + description: 'Whether the insight is favorited', + }, + }, +} diff --git a/apps/sim/tools/posthog/update_property_definition.ts b/apps/sim/tools/posthog/update_property_definition.ts index bf15e714e5c..c10b8fb6ad5 100644 --- a/apps/sim/tools/posthog/update_property_definition.ts +++ b/apps/sim/tools/posthog/update_property_definition.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogUpdatePropertyDefinitionParams { projectId: string propertyDefinitionId: string region: 'us' | 'eu' + host?: string apiKey: string description?: string tags?: string @@ -19,9 +21,7 @@ interface PropertyDefinition { is_numerical: boolean is_seen_on_filtered_events: boolean | null property_type: string - type: 'event' | 'person' | 'group' - volume_30_day: number | null - query_usage_30_day: number | null + type: 'event' | 'person' | 'group' | 'session' created_at: string updated_at: string updated_by: { @@ -34,7 +34,6 @@ interface PropertyDefinition { verified: boolean verified_at: string | null verified_by: string | null - example: string | null } export const updatePropertyDefinitionTool: ToolConfig< @@ -46,6 +45,7 @@ export const updatePropertyDefinitionTool: ToolConfig< description: 'Update a property definition in PostHog. Can modify description, tags, property type, and verification status to maintain clean property schemas.', version: '1.0.0', + errorExtractor: 'posthog-errors', params: { projectId: { @@ -66,6 +66,13 @@ export const updatePropertyDefinitionTool: ToolConfig< visibility: 'user-only', description: 'PostHog cloud region: us or eu', }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Self-hosted PostHog instance host (e.g., "posthog.mycompany.com"). Overrides the region setting when provided.', + }, apiKey: { type: 'string', required: true, @@ -100,7 +107,7 @@ export const updatePropertyDefinitionTool: ToolConfig< request: { url: (params) => { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/property_definitions/${params.propertyDefinitionId}` }, method: 'PATCH', @@ -146,15 +153,12 @@ export const updatePropertyDefinitionTool: ToolConfig< is_seen_on_filtered_events: data.is_seen_on_filtered_events ?? null, property_type: data.property_type, type: data.type, - volume_30_day: data.volume_30_day ?? null, - query_usage_30_day: data.query_usage_30_day ?? null, created_at: data.created_at, updated_at: data.updated_at, updated_by: data.updated_by ?? null, verified: data.verified || false, verified_at: data.verified_at ?? null, verified_by: data.verified_by ?? null, - example: data.example ?? null, } }, @@ -190,17 +194,7 @@ export const updatePropertyDefinitionTool: ToolConfig< }, type: { type: 'string', - description: 'Property type: event, person, or group', - }, - volume_30_day: { - type: 'number', - description: 'Number of times property was seen in the last 30 days', - optional: true, - }, - query_usage_30_day: { - type: 'number', - description: 'Number of times this property was queried in the last 30 days', - optional: true, + description: 'Property type: event, person, group, or session', }, created_at: { type: 'string', @@ -229,10 +223,5 @@ export const updatePropertyDefinitionTool: ToolConfig< description: 'User who verified the property', optional: true, }, - example: { - type: 'string', - description: 'Example value for the property', - optional: true, - }, }, } diff --git a/apps/sim/tools/posthog/update_survey.ts b/apps/sim/tools/posthog/update_survey.ts index 28fa15f69ad..7b419a21f4d 100644 --- a/apps/sim/tools/posthog/update_survey.ts +++ b/apps/sim/tools/posthog/update_survey.ts @@ -1,3 +1,4 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogSurveyQuestion { @@ -16,6 +17,7 @@ interface PostHogUpdateSurveyParams { projectId: string surveyId: string region?: 'us' | 'eu' + host?: string name?: string description?: string type?: 'popover' | 'api' @@ -62,6 +64,7 @@ export const updateSurveyTool: ToolConfig { - const baseUrl = params.region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' + const baseUrl = getPostHogAppBaseUrl(params.region, params.host) return `${baseUrl}/api/projects/${params.projectId}/surveys/${params.surveyId}/` }, method: 'PATCH', diff --git a/apps/sim/tools/posthog/utils.ts b/apps/sim/tools/posthog/utils.ts new file mode 100644 index 00000000000..0296fe41911 --- /dev/null +++ b/apps/sim/tools/posthog/utils.ts @@ -0,0 +1,40 @@ +import { validateExternalUrl } from '@/lib/core/security/input-validation' + +/** + * Shared PostHog base URL resolution. + * + * PostHog exposes two host families: + * - The "app" REST API (`/api/...`) at `us.posthog.com` / `eu.posthog.com`, authenticated + * with a personal API key. + * - The "ingest" API (`/i/v0/e`, `/batch/`, `/flags/`) at `us.i.posthog.com` / `eu.i.posthog.com`, + * authenticated with a project API key. + * + * Self-hosted PostHog instances serve both families from a single custom host. That host is + * validated with the shared SSRF guard so it can't be pointed at loopback/private/link-local + * addresses (e.g. cloud instance-metadata endpoints); the tool executor additionally + * re-validates with DNS resolution and pins the resolved IP for the actual request. + */ + +export function getPostHogAppBaseUrl(region?: 'us' | 'eu', host?: string): string { + if (host?.trim()) { + return normalizeHost(host) + } + return region === 'eu' ? 'https://eu.posthog.com' : 'https://us.posthog.com' +} + +export function getPostHogIngestBaseUrl(region?: 'us' | 'eu', host?: string): string { + if (host?.trim()) { + return normalizeHost(host) + } + return region === 'eu' ? 'https://eu.i.posthog.com' : 'https://us.i.posthog.com' +} + +function normalizeHost(host: string): string { + const trimmed = host.trim().replace(/\/+$/, '') + const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}` + const validation = validateExternalUrl(withProtocol, 'Self-hosted host') + if (!validation.isValid) { + throw new Error(`${validation.error} (e.g., posthog.mycompany.com)`) + } + return withProtocol +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f32edc49219..f82ed0e4825 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2624,12 +2624,14 @@ import { posthogCaptureEventTool, posthogCreateAnnotationTool, posthogCreateCohortTool, + posthogCreateDashboardTool, posthogCreateExperimentTool, posthogCreateFeatureFlagTool, posthogCreateInsightTool, posthogCreateSurveyTool, posthogDeleteFeatureFlagTool, posthogDeletePersonTool, + posthogDeleteSurveyTool, posthogEvaluateFlagsTool, posthogGetCohortTool, posthogGetDashboardTool, @@ -2659,8 +2661,11 @@ import { posthogListSessionRecordingsTool, posthogListSurveysTool, posthogQueryTool, + posthogUpdateCohortTool, posthogUpdateEventDefinitionTool, + posthogUpdateExperimentTool, posthogUpdateFeatureFlagTool, + posthogUpdateInsightTool, posthogUpdatePropertyDefinitionTool, posthogUpdateSurveyTool, } from '@/tools/posthog' @@ -6517,12 +6522,15 @@ export const tools: Record = { posthog_list_insights: posthogListInsightsTool, posthog_get_insight: posthogGetInsightTool, posthog_create_insight: posthogCreateInsightTool, + posthog_update_insight: posthogUpdateInsightTool, posthog_list_dashboards: posthogListDashboardsTool, posthog_get_dashboard: posthogGetDashboardTool, + posthog_create_dashboard: posthogCreateDashboardTool, posthog_list_actions: posthogListActionsTool, posthog_list_cohorts: posthogListCohortsTool, posthog_get_cohort: posthogGetCohortTool, posthog_create_cohort: posthogCreateCohortTool, + posthog_update_cohort: posthogUpdateCohortTool, posthog_list_annotations: posthogListAnnotationsTool, posthog_create_annotation: posthogCreateAnnotationTool, posthog_list_feature_flags: posthogListFeatureFlagsTool, @@ -6534,10 +6542,12 @@ export const tools: Record = { posthog_list_experiments: posthogListExperimentsTool, posthog_get_experiment: posthogGetExperimentTool, posthog_create_experiment: posthogCreateExperimentTool, + posthog_update_experiment: posthogUpdateExperimentTool, posthog_list_surveys: posthogListSurveysTool, posthog_get_survey: posthogGetSurveyTool, posthog_create_survey: posthogCreateSurveyTool, posthog_update_survey: posthogUpdateSurveyTool, + posthog_delete_survey: posthogDeleteSurveyTool, posthog_list_session_recordings: posthogListSessionRecordingsTool, posthog_get_session_recording: posthogGetSessionRecordingTool, posthog_list_recording_playlists: posthogListRecordingPlaylistsTool,