| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import { ZodError } from "zod"
- import {
- type TelemetryClient,
- type TelemetryPropertiesProvider,
- TelemetryEventName,
- type TelemetrySetting,
- } from "@roo-code/types"
- /**
- * TelemetryService wrapper class that defers initialization.
- * This ensures that we only create the various clients after environment
- * variables are loaded.
- */
- export class TelemetryService {
- constructor(private clients: TelemetryClient[]) {}
- public register(client: TelemetryClient): void {
- this.clients.push(client)
- }
- /**
- * Sets the ClineProvider reference to use for global properties
- * @param provider A ClineProvider instance to use
- */
- public setProvider(provider: TelemetryPropertiesProvider): void {
- // If client is initialized, pass the provider reference.
- if (this.isReady) {
- this.clients.forEach((client) => client.setProvider(provider))
- }
- }
- /**
- * Base method for all telemetry operations
- * Checks if the service is initialized before performing any operation
- * @returns Whether the service is ready to use
- */
- private get isReady(): boolean {
- return this.clients.length > 0
- }
- /**
- * Updates the telemetry state based on user preferences and VSCode settings
- * @param isOptedIn Whether the user is opted into telemetry
- */
- public updateTelemetryState(isOptedIn: boolean): void {
- if (!this.isReady) {
- return
- }
- this.clients.forEach((client) => client.updateTelemetryState(isOptedIn))
- }
- /**
- * Generic method to capture any type of event with specified properties
- * @param eventName The event name to capture
- * @param properties The event properties
- */
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- public captureEvent(eventName: TelemetryEventName, properties?: Record<string, any>): void {
- if (!this.isReady) {
- return
- }
- this.clients.forEach((client) => client.capture({ event: eventName, properties }))
- }
- /**
- * Captures an exception using PostHog's error tracking
- * @param error The error to capture
- * @param additionalProperties Additional properties to include with the exception
- */
- public captureException(error: Error, additionalProperties?: Record<string, unknown>): void {
- if (!this.isReady) {
- return
- }
- this.clients.forEach((client) => client.captureException(error, additionalProperties))
- }
- public captureTaskCreated(taskId: string): void {
- this.captureEvent(TelemetryEventName.TASK_CREATED, { taskId })
- }
- public captureTaskRestarted(taskId: string): void {
- this.captureEvent(TelemetryEventName.TASK_RESTARTED, { taskId })
- }
- public captureTaskCompleted(taskId: string): void {
- this.captureEvent(TelemetryEventName.TASK_COMPLETED, { taskId })
- }
- public captureConversationMessage(taskId: string, source: "user" | "assistant"): void {
- this.captureEvent(TelemetryEventName.TASK_CONVERSATION_MESSAGE, { taskId, source })
- }
- public captureLlmCompletion(
- taskId: string,
- properties: {
- inputTokens: number
- outputTokens: number
- cacheWriteTokens: number
- cacheReadTokens: number
- cost?: number
- },
- ): void {
- this.captureEvent(TelemetryEventName.LLM_COMPLETION, { taskId, ...properties })
- }
- public captureModeSwitch(taskId: string, newMode: string): void {
- this.captureEvent(TelemetryEventName.MODE_SWITCH, { taskId, newMode })
- }
- public captureToolUsage(taskId: string, tool: string): void {
- this.captureEvent(TelemetryEventName.TOOL_USED, { taskId, tool })
- }
- public captureCheckpointCreated(taskId: string): void {
- this.captureEvent(TelemetryEventName.CHECKPOINT_CREATED, { taskId })
- }
- public captureCheckpointDiffed(taskId: string): void {
- this.captureEvent(TelemetryEventName.CHECKPOINT_DIFFED, { taskId })
- }
- public captureCheckpointRestored(taskId: string): void {
- this.captureEvent(TelemetryEventName.CHECKPOINT_RESTORED, { taskId })
- }
- public captureContextCondensed(
- taskId: string,
- isAutomaticTrigger: boolean,
- usedCustomPrompt?: boolean,
- usedCustomApiHandler?: boolean,
- ): void {
- this.captureEvent(TelemetryEventName.CONTEXT_CONDENSED, {
- taskId,
- isAutomaticTrigger,
- ...(usedCustomPrompt !== undefined && { usedCustomPrompt }),
- ...(usedCustomApiHandler !== undefined && { usedCustomApiHandler }),
- })
- }
- public captureSlidingWindowTruncation(taskId: string): void {
- this.captureEvent(TelemetryEventName.SLIDING_WINDOW_TRUNCATION, { taskId })
- }
- public captureCodeActionUsed(actionType: string): void {
- this.captureEvent(TelemetryEventName.CODE_ACTION_USED, { actionType })
- }
- public capturePromptEnhanced(taskId?: string): void {
- this.captureEvent(TelemetryEventName.PROMPT_ENHANCED, { ...(taskId && { taskId }) })
- }
- public captureSchemaValidationError({ schemaName, error }: { schemaName: string; error: ZodError }): void {
- // https://zod.dev/ERROR_HANDLING?id=formatting-errors
- this.captureEvent(TelemetryEventName.SCHEMA_VALIDATION_ERROR, { schemaName, error: error.format() })
- }
- public captureDiffApplicationError(taskId: string, consecutiveMistakeCount: number): void {
- this.captureEvent(TelemetryEventName.DIFF_APPLICATION_ERROR, { taskId, consecutiveMistakeCount })
- }
- public captureShellIntegrationError(taskId: string): void {
- this.captureEvent(TelemetryEventName.SHELL_INTEGRATION_ERROR, { taskId })
- }
- public captureConsecutiveMistakeError(taskId: string): void {
- this.captureEvent(TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR, { taskId })
- }
- /**
- * Captures when a tab is shown due to user action
- * @param tab The tab that was shown
- */
- public captureTabShown(tab: string): void {
- this.captureEvent(TelemetryEventName.TAB_SHOWN, { tab })
- }
- /**
- * Captures when a setting is changed in ModesView
- * @param settingName The name of the setting that was changed
- */
- public captureModeSettingChanged(settingName: string): void {
- this.captureEvent(TelemetryEventName.MODE_SETTINGS_CHANGED, { settingName })
- }
- /**
- * Captures when a user creates a new custom mode
- * @param modeSlug The slug of the custom mode
- * @param modeName The name of the custom mode
- */
- public captureCustomModeCreated(modeSlug: string, modeName: string): void {
- this.captureEvent(TelemetryEventName.CUSTOM_MODE_CREATED, { modeSlug, modeName })
- }
- /**
- * Captures a marketplace item installation event
- * @param itemId The unique identifier of the marketplace item
- * @param itemType The type of item (mode or mcp)
- * @param itemName The human-readable name of the item
- * @param target The installation target (project or global)
- * @param properties Additional properties like hasParameters, installationMethod
- */
- public captureMarketplaceItemInstalled(
- itemId: string,
- itemType: string,
- itemName: string,
- target: string,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- properties?: Record<string, any>,
- ): void {
- this.captureEvent(TelemetryEventName.MARKETPLACE_ITEM_INSTALLED, {
- itemId,
- itemType,
- itemName,
- target,
- ...(properties || {}),
- })
- }
- /**
- * Captures a marketplace item removal event
- * @param itemId The unique identifier of the marketplace item
- * @param itemType The type of item (mode or mcp)
- * @param itemName The human-readable name of the item
- * @param target The removal target (project or global)
- */
- public captureMarketplaceItemRemoved(itemId: string, itemType: string, itemName: string, target: string): void {
- this.captureEvent(TelemetryEventName.MARKETPLACE_ITEM_REMOVED, {
- itemId,
- itemType,
- itemName,
- target,
- })
- }
- /**
- * Captures a title button click event
- * @param button The button that was clicked
- */
- public captureTitleButtonClicked(button: string): void {
- this.captureEvent(TelemetryEventName.TITLE_BUTTON_CLICKED, { button })
- }
- /**
- * Captures when telemetry settings are changed
- * @param previousSetting The previous telemetry setting
- * @param newSetting The new telemetry setting
- */
- public captureTelemetrySettingsChanged(previousSetting: TelemetrySetting, newSetting: TelemetrySetting): void {
- this.captureEvent(TelemetryEventName.TELEMETRY_SETTINGS_CHANGED, {
- previousSetting,
- newSetting,
- })
- }
- /**
- * Checks if telemetry is currently enabled
- * @returns Whether telemetry is enabled
- */
- public isTelemetryEnabled(): boolean {
- return this.isReady && this.clients.some((client) => client.isTelemetryEnabled())
- }
- public async shutdown(): Promise<void> {
- if (!this.isReady) {
- return
- }
- this.clients.forEach((client) => client.shutdown())
- }
- private static _instance: TelemetryService | null = null
- static createInstance(clients: TelemetryClient[] = []) {
- if (this._instance) {
- throw new Error("TelemetryService instance already created")
- }
- this._instance = new TelemetryService(clients)
- return this._instance
- }
- static get instance() {
- if (!this._instance) {
- throw new Error("TelemetryService not initialized")
- }
- return this._instance
- }
- static hasInstance(): boolean {
- return this._instance !== null
- }
- }
|