From b67ea54e83f3e2ae75013c52f606acd6dd3edb0d Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 09:13:12 -0700 Subject: [PATCH 1/6] fix(posthog): validate integration against live API docs, add self-hosted support + CRUD coverage Fix missing response.ok checks across ~36 tools that silently treated error bodies as success. Fix delete_feature_flag ignoring failure responses, evaluate_flags targeting the undocumented /decide endpoint without the required api_key body field, and batch_events reporting a hardcoded events_processed count. Add self-hosted host support (utils.ts) alongside the existing US/EU region selector, and complete CRUD coverage with 5 new tools: update_insight, update_cohort, update_experiment, delete_survey, create_dashboard. --- apps/sim/blocks/blocks/posthog.ts | 210 +++++++++++++- apps/sim/tools/posthog/batch_events.ts | 22 +- apps/sim/tools/posthog/capture_event.ts | 12 +- apps/sim/tools/posthog/create_annotation.ts | 32 ++- apps/sim/tools/posthog/create_cohort.ts | 34 ++- apps/sim/tools/posthog/create_dashboard.ts | 200 +++++++++++++ apps/sim/tools/posthog/create_experiment.ts | 16 +- apps/sim/tools/posthog/create_feature_flag.ts | 16 +- apps/sim/tools/posthog/create_insight.ts | 32 ++- apps/sim/tools/posthog/create_survey.ts | 30 +- apps/sim/tools/posthog/delete_feature_flag.ts | 23 +- apps/sim/tools/posthog/delete_person.ts | 11 +- apps/sim/tools/posthog/delete_survey.ts | 99 +++++++ apps/sim/tools/posthog/evaluate_flags.ts | 43 ++- apps/sim/tools/posthog/get_cohort.ts | 36 ++- apps/sim/tools/posthog/get_dashboard.ts | 33 ++- .../sim/tools/posthog/get_event_definition.ts | 16 +- apps/sim/tools/posthog/get_experiment.ts | 16 +- apps/sim/tools/posthog/get_feature_flag.ts | 16 +- apps/sim/tools/posthog/get_insight.ts | 34 ++- apps/sim/tools/posthog/get_organization.ts | 38 ++- apps/sim/tools/posthog/get_person.ts | 11 +- apps/sim/tools/posthog/get_project.ts | 46 ++- .../tools/posthog/get_property_definition.ts | 16 +- .../tools/posthog/get_session_recording.ts | 36 ++- apps/sim/tools/posthog/get_survey.ts | 30 +- apps/sim/tools/posthog/index.ts | 10 + apps/sim/tools/posthog/list_actions.ts | 25 +- apps/sim/tools/posthog/list_annotations.ts | 25 +- apps/sim/tools/posthog/list_cohorts.ts | 25 +- apps/sim/tools/posthog/list_dashboards.ts | 25 +- .../tools/posthog/list_event_definitions.ts | 16 +- apps/sim/tools/posthog/list_experiments.ts | 16 +- apps/sim/tools/posthog/list_feature_flags.ts | 16 +- apps/sim/tools/posthog/list_insights.ts | 25 +- apps/sim/tools/posthog/list_organizations.ts | 24 +- apps/sim/tools/posthog/list_persons.ts | 11 +- apps/sim/tools/posthog/list_projects.ts | 24 +- .../posthog/list_property_definitions.ts | 16 +- .../tools/posthog/list_recording_playlists.ts | 23 +- .../tools/posthog/list_session_recordings.ts | 23 +- apps/sim/tools/posthog/list_surveys.ts | 23 +- apps/sim/tools/posthog/query.ts | 11 +- apps/sim/tools/posthog/update_cohort.ts | 265 ++++++++++++++++++ .../tools/posthog/update_event_definition.ts | 16 +- apps/sim/tools/posthog/update_experiment.ts | 193 +++++++++++++ apps/sim/tools/posthog/update_feature_flag.ts | 16 +- apps/sim/tools/posthog/update_insight.ts | 264 +++++++++++++++++ .../posthog/update_property_definition.ts | 16 +- apps/sim/tools/posthog/update_survey.ts | 30 +- apps/sim/tools/posthog/utils.ts | 30 ++ apps/sim/tools/registry.ts | 10 + 52 files changed, 2215 insertions(+), 71 deletions(-) create mode 100644 apps/sim/tools/posthog/create_dashboard.ts create mode 100644 apps/sim/tools/posthog/delete_survey.ts create mode 100644 apps/sim/tools/posthog/update_cohort.ts create mode 100644 apps/sim/tools/posthog/update_experiment.ts create mode 100644 apps/sim/tools/posthog/update_insight.ts create mode 100644 apps/sim/tools/posthog/utils.ts diff --git a/apps/sim/blocks/blocks/posthog.ts b/apps/sim/blocks/blocks/posthog.ts index 42c75de04c2..18a21e651bc 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' }, @@ -113,12 +118,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 +134,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 +467,17 @@ 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'], + }, + }, + { + id: 'query', + title: 'Query (JSON)', + type: 'long-input', + placeholder: '{"kind": "HogQLQuery", "query": "SELECT ..."}', + condition: { + field: 'operation', + value: 'posthog_update_insight', }, }, { @@ -431,7 +516,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 +530,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 +574,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 +587,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 +632,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 +655,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: { @@ -603,10 +718,13 @@ Return ONLY the description text.`, field: 'operation', value: [ 'posthog_create_insight', + 'posthog_update_insight', 'posthog_create_feature_flag', 'posthog_update_feature_flag', 'posthog_create_cohort', + 'posthog_update_cohort', 'posthog_create_experiment', + 'posthog_update_experiment', ], }, wandConfig: { @@ -655,14 +773,26 @@ Return ONLY the JSON object.`, 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 +833,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,7 +925,10 @@ 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', @@ -798,12 +937,24 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 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 +974,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 +1250,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 +1304,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 +1325,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', @@ -1226,7 +1389,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 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 +1401,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 +1442,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 +1475,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/posthog/batch_events.ts b/apps/sim/tools/posthog/batch_events.ts index 0eeea5d9b76..836b9a38708 100644 --- a/apps/sim/tools/posthog/batch_events.ts +++ b/apps/sim/tools/posthog/batch_events.ts @@ -1,8 +1,10 @@ +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 +37,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', @@ -69,14 +77,20 @@ export const batchEventsTool: ToolConfig { + transformResponse: async (response: Response, params) => { if (response.ok) { const data = await response.json() + let eventsProcessed = 0 + try { + eventsProcessed = params ? JSON.parse(params.batch).length : 0 + } catch { + eventsProcessed = 0 + } return { success: true, output: { status: 'Batch events captured successfully', - events_processed: data.status === 1 ? JSON.parse(data.batch || '[]').length : 0, + events_processed: data.status === 1 ? eventsProcessed : 0, }, } } diff --git a/apps/sim/tools/posthog/capture_event.ts b/apps/sim/tools/posthog/capture_event.ts index aa0ad4c2a9d..2f0cbcd4ffa 100644 --- a/apps/sim/tools/posthog/capture_event.ts +++ b/apps/sim/tools/posthog/capture_event.ts @@ -1,8 +1,10 @@ +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 +40,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', diff --git a/apps/sim/tools/posthog/create_annotation.ts b/apps/sim/tools/posthog/create_annotation.ts index f60dc975a01..b3173a20227 100644 --- a/apps/sim/tools/posthog/create_annotation.ts +++ b/apps/sim/tools/posthog/create_annotation.ts @@ -1,9 +1,11 @@ +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 @@ -58,6 +60,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, @@ -93,7 +102,7 @@ export const createAnnotationTool: 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}/annotations/` }, method: 'POST', @@ -124,6 +133,27 @@ export const createAnnotationTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + content: '', + date_marker: '', + created_at: '', + updated_at: '', + created_by: null, + dashboard_item: null, + insight_short_id: null, + insight_name: null, + scope: '', + deleted: false, + }, + error: error || 'Failed to create annotation', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_cohort.ts b/apps/sim/tools/posthog/create_cohort.ts index 71cc283e808..50352d2c061 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 @@ -59,6 +61,13 @@ 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', @@ -151,6 +160,29 @@ export const createCohortTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + groups: [], + deleted: false, + filters: {}, + query: null, + created_at: '', + created_by: null, + is_calculating: false, + count: 0, + is_static: false, + version: 0, + }, + error: error || 'Failed to create cohort', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_dashboard.ts b/apps/sim/tools/posthog/create_dashboard.ts new file mode 100644 index 00000000000..b9112a47d68 --- /dev/null +++ b/apps/sim/tools/posthog/create_dashboard.ts @@ -0,0 +1,200 @@ +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', + + 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) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + pinned: false, + created_at: '', + tiles: [], + filters: {}, + tags: [], + }, + error: error || 'Failed to create dashboard', + } + } + + 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..5456c1fb0fa 100644 --- a/apps/sim/tools/posthog/create_experiment.ts +++ b/apps/sim/tools/posthog/create_experiment.ts @@ -1,8 +1,10 @@ +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 @@ -53,6 +55,13 @@ 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', @@ -166,6 +175,11 @@ export const createExperimentTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to create experiment') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_feature_flag.ts b/apps/sim/tools/posthog/create_feature_flag.ts index 71e95781b31..19800a27156 100644 --- a/apps/sim/tools/posthog/create_feature_flag.ts +++ b/apps/sim/tools/posthog/create_feature_flag.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface CreateFeatureFlagParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string name: string key: string @@ -50,6 +52,13 @@ export const createFeatureFlagTool: 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', @@ -137,6 +146,11 @@ export const createFeatureFlagTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to create feature flag') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_insight.ts b/apps/sim/tools/posthog/create_insight.ts index 023ba8abe13..bd12974b16d 100644 --- a/apps/sim/tools/posthog/create_insight.ts +++ b/apps/sim/tools/posthog/create_insight.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface PostHogCreateInsightParams { apiKey: string projectId: string region: string + host?: string name: string description?: string filters?: string @@ -59,6 +61,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, @@ -100,7 +109,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', @@ -152,6 +161,27 @@ export const createInsightTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + filters: {}, + query: null, + created_at: '', + created_by: null, + last_modified_at: '', + saved: false, + dashboards: [], + tags: [], + }, + error: error || 'Failed to create insight', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_survey.ts b/apps/sim/tools/posthog/create_survey.ts index f5155df2807..452f3a96605 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' @@ -81,6 +83,13 @@ 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', @@ -185,6 +194,25 @@ export const createSurveyTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + survey: { + id: '', + name: '', + description: '', + type: 'popover', + questions: [], + created_at: '', + created_by: {}, + }, + }, + error: error || 'Failed to create survey', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/delete_feature_flag.ts b/apps/sim/tools/posthog/delete_feature_flag.ts index 9e2df9110a2..fe9247b2171 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 } @@ -38,6 +40,13 @@ export const deleteFeatureFlagTool: 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/${params.flagId}` }, method: 'DELETE', @@ -59,9 +68,17 @@ export const deleteFeatureFlagTool: ToolConfig { + if (response.ok || response.status === 204) { + return { + success: true, + message: 'Feature flag deleted successfully', + } + } + + const error = await response.text() return { - success: true, - message: 'Feature flag deleted successfully', + success: false, + message: error || 'Failed to delete feature flag', } }, diff --git a/apps/sim/tools/posthog/delete_person.ts b/apps/sim/tools/posthog/delete_person.ts index ccfce302282..e83d78a1857 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 } @@ -36,6 +38,13 @@ export const deletePersonTool: 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: 'DELETE', diff --git a/apps/sim/tools/posthog/delete_survey.ts b/apps/sim/tools/posthog/delete_survey.ts new file mode 100644 index 00000000000..550edc6de85 --- /dev/null +++ b/apps/sim/tools/posthog/delete_survey.ts @@ -0,0 +1,99 @@ +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', + + 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 (response: Response) => { + if (response.ok || response.status === 204) { + return { + success: true, + output: { + status: 'Survey deleted successfully', + }, + } + } + + const error = await response.text() + return { + success: false, + output: { + status: 'Failed to delete survey', + }, + error: error || 'Unknown error occurred', + } + }, + + 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..5c1d884d419 100644 --- a/apps/sim/tools/posthog/evaluate_flags.ts +++ b/apps/sim/tools/posthog/evaluate_flags.ts @@ -1,7 +1,9 @@ +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 +35,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}`, 'Content-Type': 'application/json', }), body: (params) => { const body: Record = { + api_key: params.projectApiKey, distinct_id: params.distinctId, } @@ -110,11 +118,34 @@ export const evaluateFlagsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to evaluate feature flags') + } + 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..aa80ae6187b 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 { @@ -61,11 +63,18 @@ 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', @@ -76,6 +85,31 @@ export const getCohortTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + groups: [], + deleted: false, + filters: {}, + query: null, + created_at: '', + created_by: null, + is_calculating: false, + last_calculation: '', + errors_calculating: 0, + count: 0, + is_static: false, + version: 0, + }, + error: error || 'Failed to get cohort', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_dashboard.ts b/apps/sim/tools/posthog/get_dashboard.ts index eabeb33f0ed..dc9c98d5ab4 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 { @@ -59,11 +61,18 @@ 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', @@ -74,6 +83,28 @@ export const getDashboardTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + pinned: false, + created_at: '', + created_by: null, + last_modified_at: '', + last_modified_by: null, + tiles: [], + filters: {}, + tags: [], + restriction_level: 0, + }, + error: error || 'Failed to get dashboard', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_event_definition.ts b/apps/sim/tools/posthog/get_event_definition.ts index 323ab20fb00..ee71eaab349 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 } @@ -56,6 +58,13 @@ 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', @@ -77,6 +86,11 @@ export const getEventDefinitionTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to get event definition') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_experiment.ts b/apps/sim/tools/posthog/get_experiment.ts index a75db317727..d50cd31dae3 100644 --- a/apps/sim/tools/posthog/get_experiment.ts +++ b/apps/sim/tools/posthog/get_experiment.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface GetExperimentParams { projectId: string experimentId: string region: 'us' | 'eu' + host?: string apiKey: string } @@ -54,6 +56,13 @@ export const getExperimentTool: 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/${params.experimentId}` }, method: 'GET', @@ -75,6 +84,11 @@ export const getExperimentTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to get experiment') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_feature_flag.ts b/apps/sim/tools/posthog/get_feature_flag.ts index 8176e8ca204..834986284df 100644 --- a/apps/sim/tools/posthog/get_feature_flag.ts +++ b/apps/sim/tools/posthog/get_feature_flag.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface GetFeatureFlagParams { projectId: string flagId: string region: 'us' | 'eu' + host?: string apiKey: string } @@ -52,6 +54,13 @@ export const getFeatureFlagTool: 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/${params.flagId}` }, method: 'GET', @@ -73,6 +82,11 @@ export const getFeatureFlagTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to get feature flag') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_insight.ts b/apps/sim/tools/posthog/get_insight.ts index 89299ba345f..e44068940ae 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 { @@ -59,11 +61,18 @@ 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', @@ -74,6 +83,29 @@ export const getInsightTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + filters: {}, + query: null, + created_at: '', + created_by: null, + last_modified_at: '', + last_modified_by: null, + saved: false, + dashboards: [], + tags: [], + favorited: false, + }, + error: error || 'Failed to get insight', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_organization.ts b/apps/sim/tools/posthog/get_organization.ts index f65ce359df7..81fdf5e9e88 100644 --- a/apps/sim/tools/posthog/get_organization.ts +++ b/apps/sim/tools/posthog/get_organization.ts @@ -1,9 +1,11 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogGetOrganizationParams { organizationId: string apiKey: string region?: 'us' | 'eu' + host?: string } interface PostHogOrganizationDetail { @@ -68,11 +70,18 @@ export const getOrganizationTool: ToolConfig< visibility: 'user-only', description: 'Cloud region: us 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.', + }, }, 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/organizations/${params.organizationId}/` }, method: 'GET', @@ -83,6 +92,33 @@ export const getOrganizationTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + organization: { + id: '', + name: '', + slug: '', + created_at: '', + updated_at: '', + membership_level: 0, + plugins_access_level: 0, + teams: [], + available_product_features: [], + domain_whitelist: [], + is_member_join_email_enabled: false, + metadata: {}, + customer_id: null, + available_features: [], + usage: null, + }, + }, + error: error || 'Failed to get organization', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_person.ts b/apps/sim/tools/posthog/get_person.ts index ca3b53be7cf..ef3d910c410 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 } @@ -41,6 +43,13 @@ 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', diff --git a/apps/sim/tools/posthog/get_project.ts b/apps/sim/tools/posthog/get_project.ts index fdabb347a08..e24f466b5ee 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 { @@ -70,11 +72,18 @@ 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', @@ -85,6 +94,41 @@ export const getProjectTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + project: { + id: 0, + uuid: '', + organization: '', + api_token: '', + app_urls: [], + name: '', + slack_incoming_webhook: '', + created_at: '', + updated_at: '', + anonymize_ips: false, + completed_snippet_onboarding: false, + ingested_event: false, + test_account_filters: [], + is_demo: false, + timezone: '', + data_attributes: [], + person_display_name_properties: [], + correlation_config: {}, + autocapture_opt_out: false, + autocapture_exceptions_opt_in: false, + session_recording_opt_in: false, + capture_console_log_opt_in: false, + capture_performance_opt_in: false, + }, + }, + error: error || 'Failed to get project', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_property_definition.ts b/apps/sim/tools/posthog/get_property_definition.ts index fe491340367..7e905ff002b 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 } @@ -62,6 +64,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 +81,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', @@ -83,6 +92,11 @@ export const getPropertyDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to get property definition') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_session_recording.ts b/apps/sim/tools/posthog/get_session_recording.ts index 551bc01fdb6..775a66918c6 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 { @@ -79,11 +81,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', @@ -94,6 +103,31 @@ export const getSessionRecordingTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + recording: { + id: '', + distinct_id: '', + viewed: false, + recording_duration: 0, + active_seconds: 0, + inactive_seconds: 0, + start_time: '', + end_time: '', + click_count: 0, + keypress_count: 0, + console_log_count: 0, + console_warn_count: 0, + console_error_count: 0, + }, + }, + error: error || 'Failed to get session recording', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_survey.ts b/apps/sim/tools/posthog/get_survey.ts index 04c31bce284..409524e1782 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 { @@ -75,11 +77,18 @@ 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', @@ -90,6 +99,25 @@ export const getSurveyTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + survey: { + id: '', + name: '', + description: '', + type: 'popover', + questions: [], + created_at: '', + created_by: {}, + }, + }, + error: error || 'Failed to get survey', + } + } + const data = await response.json() return { 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..cdb646ccd7d 100644 --- a/apps/sim/tools/posthog/list_actions.ts +++ b/apps/sim/tools/posthog/list_actions.ts @@ -1,9 +1,11 @@ +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 } @@ -58,6 +60,13 @@ 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 = [] @@ -95,6 +104,20 @@ export const listActionsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + count: 0, + next: null, + previous: null, + results: [], + }, + error: error || 'Failed to list actions', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_annotations.ts b/apps/sim/tools/posthog/list_annotations.ts index a6e56140005..5e4103d50d8 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 } @@ -60,6 +62,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 +85,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 = [] @@ -97,6 +106,20 @@ export const listAnnotationsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + count: 0, + next: null, + previous: null, + results: [], + }, + error: error || 'Failed to list annotations', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_cohorts.ts b/apps/sim/tools/posthog/list_cohorts.ts index 2c310844d88..94ad1e32abb 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 } @@ -60,6 +62,13 @@ 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 = [] @@ -97,6 +106,20 @@ export const listCohortsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + count: 0, + next: null, + previous: null, + results: [], + }, + error: error || 'Failed to list cohorts', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_dashboards.ts b/apps/sim/tools/posthog/list_dashboards.ts index 7cbb2b231ac..7f36aba42fd 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 } @@ -60,6 +62,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 +85,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 = [] @@ -97,6 +106,20 @@ export const listDashboardsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + count: 0, + next: null, + previous: null, + results: [], + }, + error: error || 'Failed to list dashboards', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_event_definitions.ts b/apps/sim/tools/posthog/list_event_definitions.ts index 082f66d6db5..a6a7da13faa 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 @@ -58,6 +60,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 +95,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()) @@ -104,6 +113,11 @@ export const listEventDefinitionsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to list event definitions') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_experiments.ts b/apps/sim/tools/posthog/list_experiments.ts index a78c359652c..598fc0a49de 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 @@ -50,6 +52,13 @@ 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)) @@ -88,6 +97,11 @@ export const listExperimentsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to list experiments') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_feature_flags.ts b/apps/sim/tools/posthog/list_feature_flags.ts index 15f160c79f1..9c51a493044 100644 --- a/apps/sim/tools/posthog/list_feature_flags.ts +++ b/apps/sim/tools/posthog/list_feature_flags.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' interface ListFeatureFlagsParams { projectId: string region: 'us' | 'eu' + host?: string apiKey: string limit?: number offset?: number @@ -48,6 +50,13 @@ export const listFeatureFlagsTool: 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)) @@ -86,6 +95,11 @@ export const listFeatureFlagsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to list feature flags') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_insights.ts b/apps/sim/tools/posthog/list_insights.ts index dca948d80e9..18d23ec4123 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 } @@ -58,6 +60,13 @@ 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 = [] @@ -95,6 +104,20 @@ export const listInsightsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + count: 0, + next: null, + previous: null, + results: [], + }, + error: error || 'Failed to list insights', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_organizations.ts b/apps/sim/tools/posthog/list_organizations.ts index e2e37c8fe84..84b9564c70d 100644 --- a/apps/sim/tools/posthog/list_organizations.ts +++ b/apps/sim/tools/posthog/list_organizations.ts @@ -1,8 +1,10 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' export interface PostHogListOrganizationsParams { apiKey: string region?: 'us' | 'eu' + host?: string } interface PostHogOrganization { @@ -55,11 +57,18 @@ export const listOrganizationsTool: ToolConfig< visibility: 'user-only', description: 'Cloud region: us 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.', + }, }, 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/organizations/` }, method: 'GET', @@ -70,12 +79,23 @@ export const listOrganizationsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + organizations: [], + }, + error: error || 'Failed to list organizations', + } + } + const data = await response.json() 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..885348e66f9 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 @@ -48,6 +50,13 @@ 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()) diff --git a/apps/sim/tools/posthog/list_projects.ts b/apps/sim/tools/posthog/list_projects.ts index b3b227cc431..e74a0c03c15 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 { @@ -53,11 +55,18 @@ 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', @@ -68,12 +77,23 @@ export const listProjectsTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + projects: [], + }, + error: error || 'Failed to list projects', + } + } + const data = await response.json() return { success: true, output: { - projects: data.results.map((project: any) => ({ + 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..c8798c00630 100644 --- a/apps/sim/tools/posthog/list_property_definitions.ts +++ b/apps/sim/tools/posthog/list_property_definitions.ts @@ -1,8 +1,10 @@ +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 @@ -62,6 +64,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 +105,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()) @@ -115,6 +124,11 @@ export const listPropertyDefinitionsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to list property definitions') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_recording_playlists.ts b/apps/sim/tools/posthog/list_recording_playlists.ts index dfb2561dbad..57b259acf7b 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 } @@ -68,6 +70,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 +93,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/` ) @@ -106,6 +115,18 @@ export const listRecordingPlaylistsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + playlists: [], + count: 0, + }, + error: error || 'Failed to list recording playlists', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_session_recordings.ts b/apps/sim/tools/posthog/list_session_recordings.ts index d0967e7c292..398d2f6146b 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 } @@ -69,6 +71,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 +94,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) { @@ -105,6 +114,18 @@ export const listSessionRecordingsTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + recordings: [], + count: 0, + }, + error: error || 'Failed to list session recordings', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_surveys.ts b/apps/sim/tools/posthog/list_surveys.ts index bb4d57cfaf7..73ab784528b 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 } @@ -66,6 +68,13 @@ 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) { @@ -102,6 +111,18 @@ export const listSurveysTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + surveys: [], + count: 0, + }, + error: error || 'Failed to list surveys', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/query.ts b/apps/sim/tools/posthog/query.ts index 050dd81f14e..75da20ff278 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 @@ -40,6 +42,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 +73,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', diff --git a/apps/sim/tools/posthog/update_cohort.ts b/apps/sim/tools/posthog/update_cohort.ts new file mode 100644 index 00000000000..8769cca792d --- /dev/null +++ b/apps/sim/tools/posthog/update_cohort.ts @@ -0,0 +1,265 @@ +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', + + 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 { + body.filters = {} + } + } + + if (params.query) { + try { + body.query = JSON.parse(params.query) + } catch { + body.query = null + } + } + + if (params.isStatic !== undefined) body.is_static = params.isStatic + + if (params.groups) { + try { + body.groups = JSON.parse(params.groups) + } catch { + body.groups = [] + } + } + + if (params.deleted !== undefined) body.deleted = params.deleted + + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + groups: [], + deleted: false, + filters: {}, + query: null, + created_at: '', + is_calculating: false, + count: 0, + is_static: false, + version: 0, + }, + error: error || 'Failed to update cohort', + } + } + + 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..30ca0a62dc6 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 @@ -61,6 +63,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 +98,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', @@ -120,6 +129,11 @@ export const updateEventDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to update event definition') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_experiment.ts b/apps/sim/tools/posthog/update_experiment.ts new file mode 100644 index 00000000000..fab2e4573c3 --- /dev/null +++ b/apps/sim/tools/posthog/update_experiment.ts @@ -0,0 +1,193 @@ +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', + + 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 { + body.parameters = {} + } + } + + if (params.filters) { + try { + body.filters = JSON.parse(params.filters) + } catch { + body.filters = {} + } + } + + 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) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to update experiment') + } + + 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..0a27e6385da 100644 --- a/apps/sim/tools/posthog/update_feature_flag.ts +++ b/apps/sim/tools/posthog/update_feature_flag.ts @@ -1,9 +1,11 @@ +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 @@ -57,6 +59,13 @@ export const updateFeatureFlagTool: 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/${params.flagId}` }, method: 'PATCH', @@ -147,6 +156,11 @@ export const updateFeatureFlagTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to update feature flag') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_insight.ts b/apps/sim/tools/posthog/update_insight.ts new file mode 100644 index 00000000000..46063c133c1 --- /dev/null +++ b/apps/sim/tools/posthog/update_insight.ts @@ -0,0 +1,264 @@ +import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' +import type { ToolConfig } from '@/tools/types' + +interface PostHogUpdateInsightParams { + apiKey: string + projectId: string + insightId: string + region?: 'us' | 'eu' + host?: string + name?: string + description?: string + filters?: string + query?: string + dashboards?: string + tags?: string + favorited?: boolean +} + +interface PostHogUpdateInsightResponse { + success: boolean + output: { + id: number + name: string + description: string + filters: Record + query: Record | null + created_at: string + last_modified_at: string + saved: boolean + 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, filters, query, dashboards, tags, and favorited status.', + version: '1.0.0', + + 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', + }, + filters: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of updated filter configuration 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.filters) { + try { + body.filters = JSON.parse(params.filters) + } catch { + body.filters = {} + } + } + + if (params.query) { + try { + body.query = JSON.parse(params.query) + } catch { + body.query = null + } + } + + 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) => { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + id: 0, + name: '', + description: '', + filters: {}, + query: null, + created_at: '', + last_modified_at: '', + saved: false, + dashboards: [], + tags: [], + favorited: false, + }, + error: error || 'Failed to update insight', + } + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name || '', + description: data.description || '', + filters: data.filters || {}, + query: data.query || null, + created_at: data.created_at, + last_modified_at: data.last_modified_at, + saved: data.saved || false, + 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', + }, + filters: { + type: 'object', + description: 'Filter configuration for 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', + }, + saved: { + type: 'boolean', + description: 'Whether the insight is saved', + }, + 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..0d7f766d4d7 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 @@ -66,6 +68,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 +109,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', @@ -135,6 +144,11 @@ export const updatePropertyDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { + if (!response.ok) { + const error = await response.text() + throw new Error(error || 'Failed to update property definition') + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_survey.ts b/apps/sim/tools/posthog/update_survey.ts index 28fa15f69ad..828324484a4 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' @@ -89,6 +91,13 @@ 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', @@ -199,6 +208,25 @@ export const updateSurveyTool: ToolConfig { + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: { + survey: { + id: '', + name: '', + description: '', + type: 'popover', + questions: [], + created_at: '', + created_by: {}, + }, + }, + error: error || 'Failed to update survey', + } + } + const data = await response.json() return { diff --git a/apps/sim/tools/posthog/utils.ts b/apps/sim/tools/posthog/utils.ts new file mode 100644 index 00000000000..4e9653a9bf3 --- /dev/null +++ b/apps/sim/tools/posthog/utils.ts @@ -0,0 +1,30 @@ +/** + * 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. + */ + +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(/\/+$/, '') + return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}` +} 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, From fdecd8acf6dc23060c0fe6e78015ca90eeb11c69 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 09:49:07 -0700 Subject: [PATCH 2/6] fix(posthog): remove dead response.ok checks, fix real schema/endpoint bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior commit added if (!response.ok) branches inside transformResponse across ~40 tool files. This is dead code — tools/index.ts already throws on non-2xx (and error-payload) responses before transformResponse is ever invoked, so transformResponse only ever receives already-successful responses. Reverted to the idiomatic no-error-branch pattern used elsewhere in the codebase (e.g. hunter/email_verifier.ts), and added a posthog-errors errorExtractor so the framework's own error throw surfaces PostHog's real {type, code, detail, attr} error shape instead of a generic status message. Verified against PostHog's live OpenAPI schema, fixed real bugs: - delete_feature_flag: DELETE always returns 405 (hard delete not allowed); switched to PATCH with deleted: true - delete_person: no single-person DELETE endpoint exists; switched to POST .../persons/bulk_delete/ with ids: [personId] - create_annotation: insight_short_id is read-only on create; the writable field is dashboard_id - removed nonexistent fields (experiment variants, insight filters/saved) not present in the current schema - added missing trailing slashes on feature-flag/experiment URLs --- apps/sim/blocks/blocks/posthog.ts | 9 ---- apps/sim/tools/error-extractors.ts | 12 +++++ apps/sim/tools/posthog/batch_events.ts | 30 ++++------- apps/sim/tools/posthog/capture_event.ts | 15 +----- apps/sim/tools/posthog/create_annotation.ts | 48 +++++++---------- apps/sim/tools/posthog/create_cohort.ts | 24 +-------- apps/sim/tools/posthog/create_dashboard.ts | 19 +------ apps/sim/tools/posthog/create_experiment.ts | 27 ++-------- apps/sim/tools/posthog/create_feature_flag.ts | 6 +-- apps/sim/tools/posthog/create_insight.ts | 52 +------------------ apps/sim/tools/posthog/create_survey.ts | 24 ++------- apps/sim/tools/posthog/delete_feature_flag.ts | 22 ++++---- apps/sim/tools/posthog/delete_person.ts | 27 +++++----- apps/sim/tools/posthog/delete_survey.ts | 18 ++----- apps/sim/tools/posthog/evaluate_flags.ts | 5 -- apps/sim/tools/posthog/get_cohort.ts | 26 +--------- apps/sim/tools/posthog/get_dashboard.ts | 23 +------- .../sim/tools/posthog/get_event_definition.ts | 20 +------ apps/sim/tools/posthog/get_experiment.ts | 10 +--- apps/sim/tools/posthog/get_feature_flag.ts | 8 +-- apps/sim/tools/posthog/get_insight.ts | 38 +------------- apps/sim/tools/posthog/get_organization.ts | 28 +--------- apps/sim/tools/posthog/get_person.ts | 19 +------ apps/sim/tools/posthog/get_project.ts | 36 +------------ .../tools/posthog/get_property_definition.ts | 31 ++--------- .../tools/posthog/get_session_recording.ts | 34 +----------- apps/sim/tools/posthog/get_survey.ts | 20 +------ apps/sim/tools/posthog/list_actions.ts | 23 ++++---- apps/sim/tools/posthog/list_annotations.ts | 21 +++----- apps/sim/tools/posthog/list_cohorts.ts | 15 +----- apps/sim/tools/posthog/list_dashboards.ts | 15 +----- .../tools/posthog/list_event_definitions.ts | 20 +------ apps/sim/tools/posthog/list_experiments.ts | 8 +-- apps/sim/tools/posthog/list_feature_flags.ts | 6 +-- apps/sim/tools/posthog/list_insights.ts | 23 +------- apps/sim/tools/posthog/list_organizations.ts | 12 +---- apps/sim/tools/posthog/list_persons.ts | 12 +---- apps/sim/tools/posthog/list_projects.ts | 12 +---- .../posthog/list_property_definitions.ts | 26 ++-------- .../tools/posthog/list_recording_playlists.ts | 13 +---- .../tools/posthog/list_session_recordings.ts | 13 +---- apps/sim/tools/posthog/list_surveys.ts | 13 +---- apps/sim/tools/posthog/query.ts | 12 +---- apps/sim/tools/posthog/types.ts | 4 -- apps/sim/tools/posthog/update_cohort.ts | 23 +------- .../tools/posthog/update_event_definition.ts | 20 +------ apps/sim/tools/posthog/update_experiment.ts | 6 +-- apps/sim/tools/posthog/update_feature_flag.ts | 8 +-- apps/sim/tools/posthog/update_insight.ts | 51 +----------------- .../posthog/update_property_definition.ts | 31 ++--------- apps/sim/tools/posthog/update_survey.ts | 20 +------ 51 files changed, 140 insertions(+), 898 deletions(-) diff --git a/apps/sim/blocks/blocks/posthog.ts b/apps/sim/blocks/blocks/posthog.ts index 18a21e651bc..ba36f70f595 100644 --- a/apps/sim/blocks/blocks/posthog.ts +++ b/apps/sim/blocks/blocks/posthog.ts @@ -717,8 +717,6 @@ Return ONLY the description text.`, condition: { field: 'operation', value: [ - 'posthog_create_insight', - 'posthog_update_insight', 'posthog_create_feature_flag', 'posthog_update_feature_flag', 'posthog_create_cohort', @@ -930,13 +928,6 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 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', 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 836b9a38708..9afdc858ed5 100644 --- a/apps/sim/tools/posthog/batch_events.ts +++ b/apps/sim/tools/posthog/batch_events.ts @@ -78,31 +78,19 @@ export const batchEventsTool: ToolConfig { - if (response.ok) { - const data = await response.json() - let eventsProcessed = 0 - try { - eventsProcessed = params ? JSON.parse(params.batch).length : 0 - } catch { - eventsProcessed = 0 - } - return { - success: true, - output: { - status: 'Batch events captured successfully', - events_processed: data.status === 1 ? eventsProcessed : 0, - }, - } + const data = await response.json() + let eventsProcessed = 0 + try { + eventsProcessed = params ? JSON.parse(params.batch).length : 0 + } catch { + eventsProcessed = 0 } - - const error = await response.text() return { - success: false, + success: true, output: { - status: 'Failed to capture batch events', - events_processed: 0, + status: 'Batch events captured successfully', + events_processed: data.status === 1 ? 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 2f0cbcd4ffa..e7a78b81b7f 100644 --- a/apps/sim/tools/posthog/capture_event.ts +++ b/apps/sim/tools/posthog/capture_event.ts @@ -109,22 +109,11 @@ export const captureEventTool: ToolConfig { - if (response.ok) { - return { - success: true, - output: { - status: 'Event captured successfully', - }, - } - } - - const error = await response.text() return { - success: false, + success: true, output: { - status: 'Failed to capture event', + status: 'Event captured successfully', }, - error: error || 'Unknown error occurred', } }, diff --git a/apps/sim/tools/posthog/create_annotation.ts b/apps/sim/tools/posthog/create_annotation.ts index b3173a20227..788cd4880de 100644 --- a/apps/sim/tools/posthog/create_annotation.ts +++ b/apps/sim/tools/posthog/create_annotation.ts @@ -10,7 +10,7 @@ interface PostHogCreateAnnotationParams { date_marker: string scope?: string dashboard_item?: string - insight_short_id?: string + dashboard_id?: string } interface PostHogCreateAnnotationResponse { @@ -23,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 @@ -39,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: { @@ -84,19 +86,22 @@ 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")', }, }, @@ -124,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 @@ -133,27 +138,6 @@ export const createAnnotationTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - content: '', - date_marker: '', - created_at: '', - updated_at: '', - created_by: null, - dashboard_item: null, - insight_short_id: null, - insight_name: null, - scope: '', - deleted: false, - }, - error: error || 'Failed to create annotation', - } - } - const data = await response.json() return { @@ -166,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 || '', @@ -205,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', @@ -217,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 50352d2c061..25ebdf8fc05 100644 --- a/apps/sim/tools/posthog/create_cohort.ts +++ b/apps/sim/tools/posthog/create_cohort.ts @@ -40,6 +40,7 @@ export const createCohortTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - groups: [], - deleted: false, - filters: {}, - query: null, - created_at: '', - created_by: null, - is_calculating: false, - count: 0, - is_static: false, - version: 0, - }, - error: error || 'Failed to create cohort', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_dashboard.ts b/apps/sim/tools/posthog/create_dashboard.ts index b9112a47d68..4cfa617fd63 100644 --- a/apps/sim/tools/posthog/create_dashboard.ts +++ b/apps/sim/tools/posthog/create_dashboard.ts @@ -36,6 +36,7 @@ export const createDashboardTool: ToolConfig< 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: { @@ -128,24 +129,6 @@ export const createDashboardTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - pinned: false, - created_at: '', - tiles: [], - filters: {}, - tags: [], - }, - error: error || 'Failed to create dashboard', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_experiment.ts b/apps/sim/tools/posthog/create_experiment.ts index 5456c1fb0fa..d0899746d7e 100644 --- a/apps/sim/tools/posthog/create_experiment.ts +++ b/apps/sim/tools/posthog/create_experiment.ts @@ -11,7 +11,6 @@ interface CreateExperimentParams { featureFlagKey: string parameters?: string filters?: string - variants?: string startDate?: string endDate?: string } @@ -24,7 +23,6 @@ interface Experiment { feature_flag: Record parameters: Record filters: Record - variants: Record start_date: string | null end_date: string | null created_at: string @@ -41,6 +39,7 @@ export const createExperimentTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to create experiment') - } - const data = await response.json() return { @@ -199,7 +179,6 @@ export const createExperimentTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to create feature flag') - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/create_insight.ts b/apps/sim/tools/posthog/create_insight.ts index bd12974b16d..f959ad324fe 100644 --- a/apps/sim/tools/posthog/create_insight.ts +++ b/apps/sim/tools/posthog/create_insight.ts @@ -8,7 +8,6 @@ interface PostHogCreateInsightParams { host?: string name: string description?: string - filters?: string query?: string dashboards?: string tags?: string @@ -20,12 +19,10 @@ interface PostHogCreateInsightResponse { id: number name: string description: string - filters: Record query: Record | null created_at: string created_by: Record | null last_modified_at: string - saved: boolean dashboards: number[] tags: string[] } @@ -37,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: { @@ -81,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, @@ -126,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) @@ -161,27 +144,6 @@ export const createInsightTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - filters: {}, - query: null, - created_at: '', - created_by: null, - last_modified_at: '', - saved: false, - dashboards: [], - tags: [], - }, - error: error || 'Failed to create insight', - } - } - const data = await response.json() return { @@ -190,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 || [], }, @@ -215,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', @@ -237,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 452f3a96605..13fdc1568f8 100644 --- a/apps/sim/tools/posthog/create_survey.ts +++ b/apps/sim/tools/posthog/create_survey.ts @@ -62,6 +62,7 @@ export const createSurveyTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - survey: { - id: '', - name: '', - description: '', - type: 'popover', - questions: [], - created_at: '', - created_by: {}, - }, - }, - error: error || 'Failed to create survey', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/delete_feature_flag.ts b/apps/sim/tools/posthog/delete_feature_flag.ts index fe9247b2171..8b33a90bf23 100644 --- a/apps/sim/tools/posthog/delete_feature_flag.ts +++ b/apps/sim/tools/posthog/delete_feature_flag.ts @@ -20,6 +20,7 @@ export const deleteFeatureFlagTool: ToolConfig { const baseUrl = getPostHogAppBaseUrl(params.region, params.host) - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + 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) => { - if (response.ok || response.status === 204) { - return { - success: true, - message: 'Feature flag deleted successfully', - } - } - - const error = await response.text() + transformResponse: async () => { return { - success: false, - message: error || 'Failed to delete feature flag', + 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 e83d78a1857..3149df360eb 100644 --- a/apps/sim/tools/posthog/delete_person.ts +++ b/apps/sim/tools/posthog/delete_person.ts @@ -23,6 +23,7 @@ export const deletePersonTool: ToolConfig { const baseUrl = getPostHogAppBaseUrl(params.region, params.host) - return `${baseUrl}/api/projects/${params.projectId}/persons/${params.personId}/` + 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() return { - success: false, + success: true, output: { - status: 'Failed to delete person', + status: + data.persons_deleted > 0 + ? '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 index 550edc6de85..293f22035d7 100644 --- a/apps/sim/tools/posthog/delete_survey.ts +++ b/apps/sim/tools/posthog/delete_survey.ts @@ -22,6 +22,7 @@ export const deleteSurveyTool: ToolConfig { - if (response.ok || response.status === 204) { - return { - success: true, - output: { - status: 'Survey deleted successfully', - }, - } - } - - const error = await response.text() + transformResponse: async () => { return { - success: false, + success: true, output: { - status: 'Failed to delete survey', + status: 'Survey deleted successfully', }, - error: error || 'Unknown error occurred', } }, diff --git a/apps/sim/tools/posthog/evaluate_flags.ts b/apps/sim/tools/posthog/evaluate_flags.ts index 5c1d884d419..88a78f75490 100644 --- a/apps/sim/tools/posthog/evaluate_flags.ts +++ b/apps/sim/tools/posthog/evaluate_flags.ts @@ -118,11 +118,6 @@ export const evaluateFlagsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to evaluate feature flags') - } - const data = await response.json() const flags: Record< string, diff --git a/apps/sim/tools/posthog/get_cohort.ts b/apps/sim/tools/posthog/get_cohort.ts index aa80ae6187b..f4887544aba 100644 --- a/apps/sim/tools/posthog/get_cohort.ts +++ b/apps/sim/tools/posthog/get_cohort.ts @@ -36,6 +36,7 @@ export const getCohortTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - groups: [], - deleted: false, - filters: {}, - query: null, - created_at: '', - created_by: null, - is_calculating: false, - last_calculation: '', - errors_calculating: 0, - count: 0, - is_static: false, - version: 0, - }, - error: error || 'Failed to get cohort', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_dashboard.ts b/apps/sim/tools/posthog/get_dashboard.ts index dc9c98d5ab4..4ba2cb33168 100644 --- a/apps/sim/tools/posthog/get_dashboard.ts +++ b/apps/sim/tools/posthog/get_dashboard.ts @@ -34,6 +34,7 @@ export const getDashboardTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - pinned: false, - created_at: '', - created_by: null, - last_modified_at: '', - last_modified_by: null, - tiles: [], - filters: {}, - tags: [], - restriction_level: 0, - }, - error: error || 'Failed to get dashboard', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_event_definition.ts b/apps/sim/tools/posthog/get_event_definition.ts index ee71eaab349..ee1e0bee0e8 100644 --- a/apps/sim/tools/posthog/get_event_definition.ts +++ b/apps/sim/tools/posthog/get_event_definition.ts @@ -14,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 @@ -38,6 +36,7 @@ export const getEventDefinitionTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to get event definition') - } - const data = await response.json() return { @@ -98,8 +92,6 @@ export const getEventDefinitionTool: ToolConfig parameters: Record filters: Record - variants: Record start_date: string | null end_date: string | null created_at: string @@ -36,6 +35,7 @@ export const getExperimentTool: ToolConfig { const baseUrl = getPostHogAppBaseUrl(params.region, params.host) - return `${baseUrl}/api/projects/${params.projectId}/experiments/${params.experimentId}` + return `${baseUrl}/api/projects/${params.projectId}/experiments/${params.experimentId}/` }, method: 'GET', headers: (params) => ({ @@ -84,11 +84,6 @@ export const getExperimentTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to get experiment') - } - const data = await response.json() return { @@ -108,7 +103,6 @@ export const getExperimentTool: ToolConfig { const baseUrl = getPostHogAppBaseUrl(params.region, params.host) - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}/` }, method: 'GET', headers: (params) => ({ @@ -82,11 +83,6 @@ export const getFeatureFlagTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to get feature flag') - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_insight.ts b/apps/sim/tools/posthog/get_insight.ts index e44068940ae..cd52a54d020 100644 --- a/apps/sim/tools/posthog/get_insight.ts +++ b/apps/sim/tools/posthog/get_insight.ts @@ -15,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 @@ -32,8 +30,9 @@ export const getInsightTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - filters: {}, - query: null, - created_at: '', - created_by: null, - last_modified_at: '', - last_modified_by: null, - saved: false, - dashboards: [], - tags: [], - favorited: false, - }, - error: error || 'Failed to get insight', - } - } - const data = await response.json() return { @@ -114,13 +90,11 @@ export const getInsightTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - organization: { - id: '', - name: '', - slug: '', - created_at: '', - updated_at: '', - membership_level: 0, - plugins_access_level: 0, - teams: [], - available_product_features: [], - domain_whitelist: [], - is_member_join_email_enabled: false, - metadata: {}, - customer_id: null, - available_features: [], - usage: null, - }, - }, - error: error || 'Failed to get organization', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_person.ts b/apps/sim/tools/posthog/get_person.ts index ef3d910c410..5b022377ebf 100644 --- a/apps/sim/tools/posthog/get_person.ts +++ b/apps/sim/tools/posthog/get_person.ts @@ -28,6 +28,7 @@ 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 e24f466b5ee..9c4d4bb9aba 100644 --- a/apps/sim/tools/posthog/get_project.ts +++ b/apps/sim/tools/posthog/get_project.ts @@ -52,6 +52,7 @@ export const getProjectTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - project: { - id: 0, - uuid: '', - organization: '', - api_token: '', - app_urls: [], - name: '', - slack_incoming_webhook: '', - created_at: '', - updated_at: '', - anonymize_ips: false, - completed_snippet_onboarding: false, - ingested_event: false, - test_account_filters: [], - is_demo: false, - timezone: '', - data_attributes: [], - person_display_name_properties: [], - correlation_config: {}, - autocapture_opt_out: false, - autocapture_exceptions_opt_in: false, - session_recording_opt_in: false, - capture_console_log_opt_in: false, - capture_performance_opt_in: false, - }, - }, - error: error || 'Failed to get project', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/get_property_definition.ts b/apps/sim/tools/posthog/get_property_definition.ts index 7e905ff002b..e1d8f822951 100644 --- a/apps/sim/tools/posthog/get_property_definition.ts +++ b/apps/sim/tools/posthog/get_property_definition.ts @@ -17,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: { @@ -32,7 +30,6 @@ interface PropertyDefinition { verified: boolean verified_at: string | null verified_by: string | null - example: string | null } export const getPropertyDefinitionTool: ToolConfig< @@ -44,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: { @@ -92,11 +90,6 @@ export const getPropertyDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to get property definition') - } - const data = await response.json() return { @@ -108,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, } }, @@ -152,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', @@ -191,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 775a66918c6..831591fd11f 100644 --- a/apps/sim/tools/posthog/get_session_recording.ts +++ b/apps/sim/tools/posthog/get_session_recording.ts @@ -29,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 { @@ -53,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: { @@ -103,31 +97,6 @@ export const getSessionRecordingTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - recording: { - id: '', - distinct_id: '', - viewed: false, - recording_duration: 0, - active_seconds: 0, - inactive_seconds: 0, - start_time: '', - end_time: '', - click_count: 0, - keypress_count: 0, - console_log_count: 0, - console_warn_count: 0, - console_error_count: 0, - }, - }, - error: error || 'Failed to get session recording', - } - } - const data = await response.json() return { @@ -158,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 409524e1782..9331e634d6f 100644 --- a/apps/sim/tools/posthog/get_survey.ts +++ b/apps/sim/tools/posthog/get_survey.ts @@ -50,6 +50,7 @@ export const getSurveyTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - survey: { - id: '', - name: '', - description: '', - type: 'popover', - questions: [], - created_at: '', - created_by: {}, - }, - }, - error: error || 'Failed to get survey', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_actions.ts b/apps/sim/tools/posthog/list_actions.ts index cdb646ccd7d..1bd7a92fdc6 100644 --- a/apps/sim/tools/posthog/list_actions.ts +++ b/apps/sim/tools/posthog/list_actions.ts @@ -8,6 +8,7 @@ interface PostHogListActionsParams { host?: string limit?: number offset?: number + search?: string } interface PostHogListActionsResponse { @@ -39,6 +40,7 @@ export const listActionsTool: ToolConfig 0) { url += `?${queryParams.join('&')}` @@ -104,20 +113,6 @@ export const listActionsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - count: 0, - next: null, - previous: null, - results: [], - }, - error: error || 'Failed to list actions', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_annotations.ts b/apps/sim/tools/posthog/list_annotations.ts index 5e4103d50d8..cd3602a4647 100644 --- a/apps/sim/tools/posthog/list_annotations.ts +++ b/apps/sim/tools/posthog/list_annotations.ts @@ -24,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 @@ -41,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: { @@ -106,20 +108,6 @@ export const listAnnotationsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - count: 0, - next: null, - previous: null, - results: [], - }, - error: error || 'Failed to list annotations', - } - } - const data = await response.json() return { @@ -136,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 || '', @@ -185,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 94ad1e32abb..d7be4511de8 100644 --- a/apps/sim/tools/posthog/list_cohorts.ts +++ b/apps/sim/tools/posthog/list_cohorts.ts @@ -41,6 +41,7 @@ export const listCohortsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - count: 0, - next: null, - previous: null, - results: [], - }, - error: error || 'Failed to list cohorts', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_dashboards.ts b/apps/sim/tools/posthog/list_dashboards.ts index 7f36aba42fd..c19ffbe2cd0 100644 --- a/apps/sim/tools/posthog/list_dashboards.ts +++ b/apps/sim/tools/posthog/list_dashboards.ts @@ -41,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: { @@ -106,20 +107,6 @@ export const listDashboardsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - count: 0, - next: null, - previous: null, - results: [], - }, - error: error || 'Failed to list dashboards', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_event_definitions.ts b/apps/sim/tools/posthog/list_event_definitions.ts index a6a7da13faa..ecf5d26052b 100644 --- a/apps/sim/tools/posthog/list_event_definitions.ts +++ b/apps/sim/tools/posthog/list_event_definitions.ts @@ -16,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 @@ -46,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: { @@ -113,11 +112,6 @@ export const listEventDefinitionsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to list event definitions') - } - const data = await response.json() return { @@ -129,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, @@ -164,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 598fc0a49de..918ab47c7e7 100644 --- a/apps/sim/tools/posthog/list_experiments.ts +++ b/apps/sim/tools/posthog/list_experiments.ts @@ -18,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 @@ -38,6 +37,7 @@ export const listExperimentsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to list experiments') - } - const data = await response.json() return { @@ -126,7 +121,6 @@ export const listExperimentsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to list feature flags') - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_insights.ts b/apps/sim/tools/posthog/list_insights.ts index 18d23ec4123..15aff43ae6a 100644 --- a/apps/sim/tools/posthog/list_insights.ts +++ b/apps/sim/tools/posthog/list_insights.ts @@ -20,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[] }> } @@ -37,8 +35,9 @@ export const listInsightsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - count: 0, - next: null, - previous: null, - results: [], - }, - error: error || 'Failed to list insights', - } - } - const data = await response.json() return { @@ -130,13 +115,11 @@ export const listInsightsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - organizations: [], - }, - error: error || 'Failed to list organizations', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_persons.ts b/apps/sim/tools/posthog/list_persons.ts index 885348e66f9..442bf5b9c99 100644 --- a/apps/sim/tools/posthog/list_persons.ts +++ b/apps/sim/tools/posthog/list_persons.ts @@ -35,6 +35,7 @@ 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 e74a0c03c15..6c7493520b8 100644 --- a/apps/sim/tools/posthog/list_projects.ts +++ b/apps/sim/tools/posthog/list_projects.ts @@ -41,6 +41,7 @@ export const listProjectsTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - projects: [], - }, - error: error || 'Failed to list projects', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_property_definitions.ts b/apps/sim/tools/posthog/list_property_definitions.ts index c8798c00630..f921c0efc9e 100644 --- a/apps/sim/tools/posthog/list_property_definitions.ts +++ b/apps/sim/tools/posthog/list_property_definitions.ts @@ -9,7 +9,7 @@ interface PostHogListPropertyDefinitionsParams { limit?: number offset?: number search?: string - type?: 'event' | 'person' | 'group' + type?: 'event' | 'person' | 'group' | 'session' } interface PropertyDefinition { @@ -20,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: { @@ -50,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: { @@ -124,11 +123,6 @@ export const listPropertyDefinitionsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to list property definitions') - } - const data = await response.json() return { @@ -144,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, @@ -185,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 57b259acf7b..e1addcdb1ba 100644 --- a/apps/sim/tools/posthog/list_recording_playlists.ts +++ b/apps/sim/tools/posthog/list_recording_playlists.ts @@ -49,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: { @@ -115,18 +116,6 @@ export const listRecordingPlaylistsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - playlists: [], - count: 0, - }, - error: error || 'Failed to list recording playlists', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_session_recordings.ts b/apps/sim/tools/posthog/list_session_recordings.ts index 398d2f6146b..0e7eb433a1e 100644 --- a/apps/sim/tools/posthog/list_session_recordings.ts +++ b/apps/sim/tools/posthog/list_session_recordings.ts @@ -50,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: { @@ -114,18 +115,6 @@ export const listSessionRecordingsTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - recordings: [], - count: 0, - }, - error: error || 'Failed to list session recordings', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/list_surveys.ts b/apps/sim/tools/posthog/list_surveys.ts index 73ab784528b..4ea049f0b60 100644 --- a/apps/sim/tools/posthog/list_surveys.ts +++ b/apps/sim/tools/posthog/list_surveys.ts @@ -47,6 +47,7 @@ export const listSurveysTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - surveys: [], - count: 0, - }, - error: error || 'Failed to list surveys', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/query.ts b/apps/sim/tools/posthog/query.ts index 75da20ff278..ae9fed885f5 100644 --- a/apps/sim/tools/posthog/query.ts +++ b/apps/sim/tools/posthog/query.ts @@ -27,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: { @@ -110,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 index 8769cca792d..ac7a3816e15 100644 --- a/apps/sim/tools/posthog/update_cohort.ts +++ b/apps/sim/tools/posthog/update_cohort.ts @@ -41,6 +41,7 @@ export const updateCohortTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - groups: [], - deleted: false, - filters: {}, - query: null, - created_at: '', - is_calculating: false, - count: 0, - is_static: false, - version: 0, - }, - error: error || 'Failed to update cohort', - } - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_event_definition.ts b/apps/sim/tools/posthog/update_event_definition.ts index 30ca0a62dc6..3ca149cd0e6 100644 --- a/apps/sim/tools/posthog/update_event_definition.ts +++ b/apps/sim/tools/posthog/update_event_definition.ts @@ -17,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 @@ -43,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: { @@ -129,11 +128,6 @@ export const updateEventDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to update event definition') - } - const data = await response.json() return { @@ -141,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, @@ -170,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 index fab2e4573c3..5af8deadeb5 100644 --- a/apps/sim/tools/posthog/update_experiment.ts +++ b/apps/sim/tools/posthog/update_experiment.ts @@ -40,6 +40,7 @@ export const updateExperimentTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to update experiment') - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_feature_flag.ts b/apps/sim/tools/posthog/update_feature_flag.ts index 0a27e6385da..92f2f616614 100644 --- a/apps/sim/tools/posthog/update_feature_flag.ts +++ b/apps/sim/tools/posthog/update_feature_flag.ts @@ -39,6 +39,7 @@ export const updateFeatureFlagTool: ToolConfig { const baseUrl = getPostHogAppBaseUrl(params.region, params.host) - return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}` + return `${baseUrl}/api/projects/${params.projectId}/feature_flags/${params.flagId}/` }, method: 'PATCH', headers: (params) => ({ @@ -156,11 +157,6 @@ export const updateFeatureFlagTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to update feature flag') - } - const data = await response.json() return { diff --git a/apps/sim/tools/posthog/update_insight.ts b/apps/sim/tools/posthog/update_insight.ts index 46063c133c1..e9916015ed6 100644 --- a/apps/sim/tools/posthog/update_insight.ts +++ b/apps/sim/tools/posthog/update_insight.ts @@ -9,7 +9,6 @@ interface PostHogUpdateInsightParams { host?: string name?: string description?: string - filters?: string query?: string dashboards?: string tags?: string @@ -22,11 +21,9 @@ interface PostHogUpdateInsightResponse { id: number name: string description: string - filters: Record query: Record | null created_at: string last_modified_at: string - saved: boolean dashboards: number[] tags: string[] favorited: boolean @@ -40,8 +37,9 @@ export const updateInsightTool: ToolConfig< id: 'posthog_update_insight', name: 'PostHog Update Insight', description: - 'Update an existing insight in PostHog. Can modify name, description, filters, query, dashboards, tags, and favorited status.', + '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: { @@ -88,12 +86,6 @@ export const updateInsightTool: ToolConfig< visibility: 'user-or-llm', description: 'Updated description for the insight', }, - filters: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'JSON string of updated filter configuration for the insight', - }, query: { type: 'string', required: false, @@ -136,14 +128,6 @@ export const updateInsightTool: ToolConfig< 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 { - body.filters = {} - } - } - if (params.query) { try { body.query = JSON.parse(params.query) @@ -173,27 +157,6 @@ export const updateInsightTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - id: 0, - name: '', - description: '', - filters: {}, - query: null, - created_at: '', - last_modified_at: '', - saved: false, - dashboards: [], - tags: [], - favorited: false, - }, - error: error || 'Failed to update insight', - } - } - const data = await response.json() return { @@ -202,11 +165,9 @@ export const updateInsightTool: ToolConfig< id: data.id, name: data.name || '', description: data.description || '', - filters: data.filters || {}, query: data.query || null, created_at: data.created_at, last_modified_at: data.last_modified_at, - saved: data.saved || false, dashboards: data.dashboards || [], tags: data.tags || [], favorited: data.favorited || false, @@ -227,10 +188,6 @@ export const updateInsightTool: 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', @@ -244,10 +201,6 @@ export const updateInsightTool: 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/update_property_definition.ts b/apps/sim/tools/posthog/update_property_definition.ts index 0d7f766d4d7..c10b8fb6ad5 100644 --- a/apps/sim/tools/posthog/update_property_definition.ts +++ b/apps/sim/tools/posthog/update_property_definition.ts @@ -21,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: { @@ -36,7 +34,6 @@ interface PropertyDefinition { verified: boolean verified_at: string | null verified_by: string | null - example: string | null } export const updatePropertyDefinitionTool: ToolConfig< @@ -48,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: { @@ -144,11 +142,6 @@ export const updatePropertyDefinitionTool: ToolConfig< }, transformResponse: async (response: Response) => { - if (!response.ok) { - const error = await response.text() - throw new Error(error || 'Failed to update property definition') - } - const data = await response.json() return { @@ -160,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, } }, @@ -204,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', @@ -243,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 828324484a4..7b419a21f4d 100644 --- a/apps/sim/tools/posthog/update_survey.ts +++ b/apps/sim/tools/posthog/update_survey.ts @@ -64,6 +64,7 @@ export const updateSurveyTool: ToolConfig { - if (!response.ok) { - const error = await response.text() - return { - success: false, - output: { - survey: { - id: '', - name: '', - description: '', - type: 'popover', - questions: [], - created_at: '', - created_by: {}, - }, - }, - error: error || 'Failed to update survey', - } - } - const data = await response.json() return { From 7ebcc19abaf40f131f4a58e5e7ba3897c3e4f5a3 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 09:53:39 -0700 Subject: [PATCH 3/6] fix(posthog): reject unsafe self-hosted host values (SSRF) The self-hosted host field accepted any string and only stripped trailing slashes before prepending https://, so a workflow could point it at loopback/private/link-local addresses (e.g. cloud instance- metadata endpoints) and the executor would make a real server-side request to it. Reuse the shared validateExternalUrl SSRF guard, same pattern already used for Convex's custom deployment URL (tools/convex/utils.ts); the tool executor separately re-validates with DNS resolution and pins the resolved IP for the actual request. Also drop an unused params arg in evaluate_flags' headers function. --- apps/sim/tools/posthog/evaluate_flags.ts | 2 +- apps/sim/tools/posthog/utils.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/sim/tools/posthog/evaluate_flags.ts b/apps/sim/tools/posthog/evaluate_flags.ts index 88a78f75490..3e8cbc7ad7d 100644 --- a/apps/sim/tools/posthog/evaluate_flags.ts +++ b/apps/sim/tools/posthog/evaluate_flags.ts @@ -80,7 +80,7 @@ export const evaluateFlagsTool: ToolConfig ({ + headers: () => ({ 'Content-Type': 'application/json', }), body: (params) => { diff --git a/apps/sim/tools/posthog/utils.ts b/apps/sim/tools/posthog/utils.ts index 4e9653a9bf3..0296fe41911 100644 --- a/apps/sim/tools/posthog/utils.ts +++ b/apps/sim/tools/posthog/utils.ts @@ -1,3 +1,5 @@ +import { validateExternalUrl } from '@/lib/core/security/input-validation' + /** * Shared PostHog base URL resolution. * @@ -7,7 +9,10 @@ * - 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. + * 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 { @@ -26,5 +31,10 @@ export function getPostHogIngestBaseUrl(region?: 'us' | 'eu', host?: string): st function normalizeHost(host: string): string { const trimmed = host.trim().replace(/\/+$/, '') - return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}` + 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 } From df10eb77f48f789ce9c675cdb8828c07497ce051 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 10:03:15 -0700 Subject: [PATCH 4/6] fix(posthog): fail loudly instead of silently dropping data on bad JSON params Several tools caught JSON.parse failures on user-supplied filter/query/ parameters strings and silently substituted {} or null, which would wipe the corresponding field on the PATCH/create request instead of surfacing an error (Cursor Bugbot flagged this for update_cohort, update_experiment, update_insight; the same pattern existed in their create_* / update_feature_flag / evaluate_flags counterparts, fixed for consistency). Now throws a descriptive error, matching the existing convention in tools/notion/query_database.ts. Also fixes batch_events: the request body silently sent an empty batch on invalid JSON, and transformResponse always reported "captured successfully" even when PostHog's response indicated failure (data.status !== 1). --- apps/sim/tools/posthog/batch_events.ts | 29 ++++++++++--------- apps/sim/tools/posthog/create_experiment.ts | 9 +++--- apps/sim/tools/posthog/create_feature_flag.ts | 5 ++-- apps/sim/tools/posthog/evaluate_flags.ts | 13 +++++---- apps/sim/tools/posthog/update_cohort.ts | 13 +++++---- apps/sim/tools/posthog/update_experiment.ts | 9 +++--- apps/sim/tools/posthog/update_feature_flag.ts | 5 ++-- apps/sim/tools/posthog/update_insight.ts | 5 ++-- 8 files changed, 49 insertions(+), 39 deletions(-) diff --git a/apps/sim/tools/posthog/batch_events.ts b/apps/sim/tools/posthog/batch_events.ts index 9afdc858ed5..0e16fea5951 100644 --- a/apps/sim/tools/posthog/batch_events.ts +++ b/apps/sim/tools/posthog/batch_events.ts @@ -1,3 +1,4 @@ +import { getErrorMessage } from '@sim/utils/errors' import { getPostHogIngestBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' @@ -63,33 +64,35 @@ 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, params) => { const data = await response.json() - let eventsProcessed = 0 - try { - eventsProcessed = params ? JSON.parse(params.batch).length : 0 - } catch { - eventsProcessed = 0 - } + const eventsProcessed = params ? (JSON.parse(params.batch) as unknown[]).length : 0 + const success = data.status === 1 + return { - success: true, + success, output: { - status: 'Batch events captured successfully', - events_processed: data.status === 1 ? eventsProcessed : 0, + status: success + ? 'Batch events captured successfully' + : `Batch events capture failed (status: ${data.status})`, + events_processed: success ? eventsProcessed : 0, }, } }, diff --git a/apps/sim/tools/posthog/create_experiment.ts b/apps/sim/tools/posthog/create_experiment.ts index d0899746d7e..49cc494d39d 100644 --- a/apps/sim/tools/posthog/create_experiment.ts +++ b/apps/sim/tools/posthog/create_experiment.ts @@ -1,3 +1,4 @@ +import { getErrorMessage } from '@sim/utils/errors' import { getPostHogAppBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' @@ -134,16 +135,16 @@ export const createExperimentTool: ToolConfig Date: Tue, 7 Jul 2026 10:05:21 -0700 Subject: [PATCH 5/6] fix(posthog): mark region required for update_experiment posthog_update_experiment was the only experiment operation missing from the region field's required-condition list; the other three (create/get/list) already require it. Region is unconditionally visible on this block (no condition key gates it), so this was a required-ness inconsistency rather than a functional bug, but users updating an EU experiment should still be prompted to select the region explicitly rather than relying on the default. --- apps/sim/blocks/blocks/posthog.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sim/blocks/blocks/posthog.ts b/apps/sim/blocks/blocks/posthog.ts index ba36f70f595..af8369fe53c 100644 --- a/apps/sim/blocks/blocks/posthog.ts +++ b/apps/sim/blocks/blocks/posthog.ts @@ -100,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 From 98d6febc566665c13536d821fa6555481611284f Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 10:20:19 -0700 Subject: [PATCH 6/6] fix(posthog): fix cross-field value leakage and false-success reports Merges update_insight's query subblock into the same insightQuery id already used by create_insight (Cursor flagged: it previously reused the 'query' subblock id shared by posthog_query's HogQL field and the cohort query fields, so switching operations could carry a stale HogQL/cohort-JSON value into an insight PATCH). Matches the existing merged-condition pattern used elsewhere in this block instead of duplicating the subblock id. Also fixes two more false-success reports in the same class as the batch_events/delete_feature_flag fixes: capture_event now checks the ingest response's status field (same {"status": 1} contract as /batch/) instead of unconditionally returning success, and delete_person now reports failure when persons_deleted is 0 instead of always success: true. --- apps/sim/blocks/blocks/posthog.ts | 21 +++++++++------------ apps/sim/tools/posthog/capture_event.ts | 14 ++++++++++---- apps/sim/tools/posthog/delete_person.ts | 8 +++----- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/apps/sim/blocks/blocks/posthog.ts b/apps/sim/blocks/blocks/posthog.ts index af8369fe53c..40fb4ecb07d 100644 --- a/apps/sim/blocks/blocks/posthog.ts +++ b/apps/sim/blocks/blocks/posthog.ts @@ -471,16 +471,6 @@ Return ONLY the JSON array.`, value: ['posthog_create_cohort', 'posthog_update_cohort'], }, }, - { - id: 'query', - title: 'Query (JSON)', - type: 'long-input', - placeholder: '{"kind": "HogQLQuery", "query": "SELECT ..."}', - condition: { - field: 'operation', - value: 'posthog_update_insight', - }, - }, { id: 'values', title: 'Query Values (JSON)', @@ -765,7 +755,10 @@ 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', @@ -1377,7 +1370,11 @@ 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 } diff --git a/apps/sim/tools/posthog/capture_event.ts b/apps/sim/tools/posthog/capture_event.ts index e7a78b81b7f..154f89c5e41 100644 --- a/apps/sim/tools/posthog/capture_event.ts +++ b/apps/sim/tools/posthog/capture_event.ts @@ -1,3 +1,4 @@ +import { getErrorMessage } from '@sim/utils/errors' import { getPostHogIngestBaseUrl } from '@/tools/posthog/utils' import type { ToolConfig } from '@/tools/types' @@ -95,8 +96,8 @@ export const captureEventTool: ToolConfig { + const data = await response.json() + const success = data.status === 1 + return { - success: true, + success, output: { - status: 'Event captured successfully', + status: success + ? 'Event captured successfully' + : `Event capture failed (status: ${data.status})`, }, } }, diff --git a/apps/sim/tools/posthog/delete_person.ts b/apps/sim/tools/posthog/delete_person.ts index 3149df360eb..262b9954f75 100644 --- a/apps/sim/tools/posthog/delete_person.ts +++ b/apps/sim/tools/posthog/delete_person.ts @@ -77,13 +77,11 @@ export const deletePersonTool: ToolConfig { const data = await response.json() + const success = data.persons_deleted > 0 return { - success: true, + success, output: { - status: - data.persons_deleted > 0 - ? 'Person deleted successfully' - : 'No matching person found to delete', + status: success ? 'Person deleted successfully' : 'No matching person found to delete', }, } },