|
|
@@ -1,152 +1,7 @@
|
|
|
-import { PostHog } from "posthog-node"
|
|
|
-import * as vscode from "vscode"
|
|
|
import { ZodError } from "zod"
|
|
|
|
|
|
import { logger } from "../../utils/logging"
|
|
|
-
|
|
|
-// This forward declaration is needed to avoid circular dependencies
|
|
|
-interface ClineProviderInterface {
|
|
|
- // Gets telemetry properties to attach to every event
|
|
|
- getTelemetryProperties(): Promise<Record<string, any>>
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * PostHogClient handles telemetry event tracking for the Roo Code extension
|
|
|
- * Uses PostHog analytics to track user interactions and system events
|
|
|
- * Respects user privacy settings and VSCode's global telemetry configuration
|
|
|
- */
|
|
|
-class PostHogClient {
|
|
|
- public static readonly EVENTS = {
|
|
|
- TASK: {
|
|
|
- CREATED: "Task Created",
|
|
|
- RESTARTED: "Task Reopened",
|
|
|
- COMPLETED: "Task Completed",
|
|
|
- CONVERSATION_MESSAGE: "Conversation Message",
|
|
|
- MODE_SWITCH: "Mode Switched",
|
|
|
- TOOL_USED: "Tool Used",
|
|
|
- CHECKPOINT_CREATED: "Checkpoint Created",
|
|
|
- CHECKPOINT_RESTORED: "Checkpoint Restored",
|
|
|
- CHECKPOINT_DIFFED: "Checkpoint Diffed",
|
|
|
- CODE_ACTION_USED: "Code Action Used",
|
|
|
- PROMPT_ENHANCED: "Prompt Enhanced",
|
|
|
- },
|
|
|
- ERRORS: {
|
|
|
- SCHEMA_VALIDATION_ERROR: "Schema Validation Error",
|
|
|
- DIFF_APPLICATION_ERROR: "Diff Application Error",
|
|
|
- CONSECUTIVE_MISTAKE_ERROR: "Consecutive Mistake Error",
|
|
|
- },
|
|
|
- }
|
|
|
-
|
|
|
- private static instance: PostHogClient
|
|
|
- private client: PostHog
|
|
|
- private distinctId: string = vscode.env.machineId
|
|
|
- private telemetryEnabled: boolean = false
|
|
|
- private providerRef: WeakRef<ClineProviderInterface> | null = null
|
|
|
-
|
|
|
- private constructor() {
|
|
|
- this.client = new PostHog(process.env.POSTHOG_API_KEY || "", {
|
|
|
- host: "https://us.i.posthog.com",
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Updates the telemetry state based on user preferences and VSCode settings
|
|
|
- * Only enables telemetry if both VSCode global telemetry is enabled and user has opted in
|
|
|
- * @param didUserOptIn Whether the user has explicitly opted into telemetry
|
|
|
- */
|
|
|
- public updateTelemetryState(didUserOptIn: boolean): void {
|
|
|
- this.telemetryEnabled = false
|
|
|
-
|
|
|
- // First check global telemetry level - telemetry should only be enabled when level is "all"
|
|
|
- const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
|
|
|
- const globalTelemetryEnabled = telemetryLevel === "all"
|
|
|
-
|
|
|
- // We only enable telemetry if global vscode telemetry is enabled
|
|
|
- if (globalTelemetryEnabled) {
|
|
|
- this.telemetryEnabled = didUserOptIn
|
|
|
- }
|
|
|
-
|
|
|
- // Update PostHog client state based on telemetry preference
|
|
|
- if (this.telemetryEnabled) {
|
|
|
- this.client.optIn()
|
|
|
- } else {
|
|
|
- this.client.optOut()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Gets or creates the singleton instance of PostHogClient
|
|
|
- * @returns The PostHogClient instance
|
|
|
- */
|
|
|
- public static getInstance(): PostHogClient {
|
|
|
- if (!PostHogClient.instance) {
|
|
|
- PostHogClient.instance = new PostHogClient()
|
|
|
- }
|
|
|
- return PostHogClient.instance
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Sets the ClineProvider reference to use for global properties
|
|
|
- * @param provider A ClineProvider instance to use
|
|
|
- */
|
|
|
- public setProvider(provider: ClineProviderInterface): void {
|
|
|
- this.providerRef = new WeakRef(provider)
|
|
|
- logger.debug("PostHogClient: ClineProvider reference set")
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Captures a telemetry event if telemetry is enabled
|
|
|
- * @param event The event to capture with its properties
|
|
|
- */
|
|
|
- public async capture(event: { event: string; properties?: any }): Promise<void> {
|
|
|
- // Only send events if telemetry is enabled
|
|
|
- if (this.telemetryEnabled) {
|
|
|
- // Get global properties from ClineProvider if available
|
|
|
- let globalProperties: Record<string, any> = {}
|
|
|
- const provider = this.providerRef?.deref()
|
|
|
-
|
|
|
- if (provider) {
|
|
|
- try {
|
|
|
- // Get the telemetry properties directly from the provider
|
|
|
- globalProperties = await provider.getTelemetryProperties()
|
|
|
- } catch (error) {
|
|
|
- // Log error but continue with capturing the event
|
|
|
- logger.error(
|
|
|
- `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`,
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Merge global properties with event-specific properties
|
|
|
- // Event properties take precedence in case of conflicts
|
|
|
- const mergedProperties = {
|
|
|
- ...globalProperties,
|
|
|
- ...(event.properties || {}),
|
|
|
- }
|
|
|
-
|
|
|
- this.client.capture({
|
|
|
- distinctId: this.distinctId,
|
|
|
- event: event.event,
|
|
|
- properties: mergedProperties,
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Checks if telemetry is currently enabled
|
|
|
- * @returns Whether telemetry is enabled
|
|
|
- */
|
|
|
- public isTelemetryEnabled(): boolean {
|
|
|
- return this.telemetryEnabled
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shuts down the PostHog client
|
|
|
- */
|
|
|
- public async shutdown(): Promise<void> {
|
|
|
- await this.client.shutdown()
|
|
|
- }
|
|
|
-}
|
|
|
+import { PostHogClient, ClineProviderInterface } from "./PostHogClient"
|
|
|
|
|
|
/**
|
|
|
* TelemetryService wrapper class that defers PostHogClient initialization
|
|
|
@@ -155,7 +10,6 @@ class PostHogClient {
|
|
|
class TelemetryService {
|
|
|
private client: PostHogClient | null = null
|
|
|
private initialized = false
|
|
|
- private providerRef: WeakRef<ClineProviderInterface> | null = null
|
|
|
|
|
|
/**
|
|
|
* Initialize the telemetry service with the PostHog client
|
|
|
@@ -179,12 +33,11 @@ class TelemetryService {
|
|
|
* @param provider A ClineProvider instance to use
|
|
|
*/
|
|
|
public setProvider(provider: ClineProviderInterface): void {
|
|
|
- // Keep a weak reference to avoid memory leaks
|
|
|
- this.providerRef = new WeakRef(provider)
|
|
|
// If client is initialized, pass the provider reference
|
|
|
- if (this.isReady()) {
|
|
|
+ if (this.isReady) {
|
|
|
this.client!.setProvider(provider)
|
|
|
}
|
|
|
+
|
|
|
logger.debug("TelemetryService: ClineProvider reference set")
|
|
|
}
|
|
|
|
|
|
@@ -193,7 +46,7 @@ class TelemetryService {
|
|
|
* Checks if the service is initialized before performing any operation
|
|
|
* @returns Whether the service is ready to use
|
|
|
*/
|
|
|
- private isReady(): boolean {
|
|
|
+ private get isReady(): boolean {
|
|
|
return this.initialized && this.client !== null
|
|
|
}
|
|
|
|
|
|
@@ -202,7 +55,10 @@ class TelemetryService {
|
|
|
* @param didUserOptIn Whether the user has explicitly opted into telemetry
|
|
|
*/
|
|
|
public updateTelemetryState(didUserOptIn: boolean): void {
|
|
|
- if (!this.isReady()) return
|
|
|
+ if (!this.isReady) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.client!.updateTelemetryState(didUserOptIn)
|
|
|
}
|
|
|
|
|
|
@@ -211,7 +67,10 @@ class TelemetryService {
|
|
|
* @param event The event to capture with its properties
|
|
|
*/
|
|
|
public capture(event: { event: string; properties?: any }): void {
|
|
|
- if (!this.isReady()) return
|
|
|
+ if (!this.isReady) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.client!.capture(event)
|
|
|
}
|
|
|
|
|
|
@@ -238,24 +97,15 @@ class TelemetryService {
|
|
|
}
|
|
|
|
|
|
public captureConversationMessage(taskId: string, source: "user" | "assistant"): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.TASK.CONVERSATION_MESSAGE, {
|
|
|
- taskId,
|
|
|
- source,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.TASK.CONVERSATION_MESSAGE, { taskId, source })
|
|
|
}
|
|
|
|
|
|
public captureModeSwitch(taskId: string, newMode: string): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.TASK.MODE_SWITCH, {
|
|
|
- taskId,
|
|
|
- newMode,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.TASK.MODE_SWITCH, { taskId, newMode })
|
|
|
}
|
|
|
|
|
|
public captureToolUsage(taskId: string, tool: string): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.TASK.TOOL_USED, {
|
|
|
- taskId,
|
|
|
- tool,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.TASK.TOOL_USED, { taskId, tool })
|
|
|
}
|
|
|
|
|
|
public captureCheckpointCreated(taskId: string): void {
|
|
|
@@ -271,36 +121,24 @@ class TelemetryService {
|
|
|
}
|
|
|
|
|
|
public captureCodeActionUsed(actionType: string): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.TASK.CODE_ACTION_USED, {
|
|
|
- actionType,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.TASK.CODE_ACTION_USED, { actionType })
|
|
|
}
|
|
|
|
|
|
public capturePromptEnhanced(taskId?: string): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.TASK.PROMPT_ENHANCED, {
|
|
|
- ...(taskId && { taskId }),
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.TASK.PROMPT_ENHANCED, { ...(taskId && { taskId }) })
|
|
|
}
|
|
|
|
|
|
public captureSchemaValidationError({ schemaName, error }: { schemaName: string; error: ZodError }): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.ERRORS.SCHEMA_VALIDATION_ERROR, {
|
|
|
- schemaName,
|
|
|
- // https://zod.dev/ERROR_HANDLING?id=formatting-errors
|
|
|
- error: error.format(),
|
|
|
- })
|
|
|
+ // https://zod.dev/ERROR_HANDLING?id=formatting-errors
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.ERRORS.SCHEMA_VALIDATION_ERROR, { schemaName, error: error.format() })
|
|
|
}
|
|
|
|
|
|
public captureDiffApplicationError(taskId: string, consecutiveMistakeCount: number): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.ERRORS.DIFF_APPLICATION_ERROR, {
|
|
|
- taskId,
|
|
|
- consecutiveMistakeCount,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.ERRORS.DIFF_APPLICATION_ERROR, { taskId, consecutiveMistakeCount })
|
|
|
}
|
|
|
|
|
|
public captureConsecutiveMistakeError(taskId: string): void {
|
|
|
- this.captureEvent(PostHogClient.EVENTS.ERRORS.CONSECUTIVE_MISTAKE_ERROR, {
|
|
|
- taskId,
|
|
|
- })
|
|
|
+ this.captureEvent(PostHogClient.EVENTS.ERRORS.CONSECUTIVE_MISTAKE_ERROR, { taskId })
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -308,15 +146,17 @@ class TelemetryService {
|
|
|
* @returns Whether telemetry is enabled
|
|
|
*/
|
|
|
public isTelemetryEnabled(): boolean {
|
|
|
- if (!this.isReady()) return false
|
|
|
- return this.client!.isTelemetryEnabled()
|
|
|
+ return this.isReady && this.client!.isTelemetryEnabled()
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Shuts down the PostHog client
|
|
|
*/
|
|
|
public async shutdown(): Promise<void> {
|
|
|
- if (!this.isReady()) return
|
|
|
+ if (!this.isReady) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
await this.client!.shutdown()
|
|
|
}
|
|
|
}
|