diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 3d1f980c7d8..65e053c3d19 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -3798,6 +3798,8 @@ export class Cline { return } + telemetryService.captureCheckpointDiffed(this.taskId) + if (!previousCommitHash && mode === "checkpoint") { const previousCheckpoint = this.clineMessages .filter(({ say }) => say === "checkpoint_saved") @@ -3849,6 +3851,8 @@ export class Cline { return } + telemetryService.captureCheckpointCreated(this.taskId) + // Start the checkpoint process in the background. service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`).catch((err) => { console.error("[Cline#checkpointSave] caught unexpected error, disabling checkpoints", err) @@ -3880,6 +3884,8 @@ export class Cline { try { await service.restoreCheckpoint(commitHash) + telemetryService.captureCheckpointRestored(this.taskId) + await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash }) if (mode === "restore") { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b82a6a62e01..72cf56d35b8 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2567,6 +2567,15 @@ export class ClineProvider implements vscode.WebviewViewProvider { properties.apiProvider = apiConfiguration.apiProvider } + // Add model ID if available + const currentCline = this.getCurrentCline() + if (currentCline?.api) { + const { id: modelId } = currentCline.api.getModel() + if (modelId) { + properties.modelId = modelId + } + } + return properties } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index f9fc5d3ece5..2e9fcdf3364 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -1652,3 +1652,62 @@ describe("ContextProxy integration", () => { expect(mockContextProxy.setValues).toBeDefined() }) }) + +describe("getTelemetryProperties", () => { + let provider: ClineProvider + let mockContext: vscode.ExtensionContext + let mockOutputChannel: vscode.OutputChannel + let mockCline: any + + beforeEach(() => { + // Reset mocks + jest.clearAllMocks() + + // Setup basic mocks + mockContext = { + globalState: { + get: jest.fn().mockImplementation((key: string) => { + if (key === "mode") return "code" + if (key === "apiProvider") return "anthropic" + return undefined + }), + update: jest.fn(), + keys: jest.fn().mockReturnValue([]), + }, + secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, + extensionUri: {} as vscode.Uri, + globalStorageUri: { fsPath: "/test/path" }, + extension: { packageJSON: { version: "1.0.0" } }, + } as unknown as vscode.ExtensionContext + + mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel + provider = new ClineProvider(mockContext, mockOutputChannel) + + // Setup Cline instance with mocked getModel method + const { Cline } = require("../../Cline") + mockCline = new Cline() + mockCline.api = { + getModel: jest.fn().mockReturnValue({ + id: "claude-3-7-sonnet-20250219", + info: { contextWindow: 200000 }, + }), + } + }) + + test("includes basic properties in telemetry", async () => { + const properties = await provider.getTelemetryProperties() + + expect(properties).toHaveProperty("vscodeVersion") + expect(properties).toHaveProperty("platform") + expect(properties).toHaveProperty("appVersion", "1.0.0") + }) + + test("includes model ID from current Cline instance if available", async () => { + // Add mock Cline to stack + await provider.addClineToStack(mockCline) + + const properties = await provider.getTelemetryProperties() + + expect(properties).toHaveProperty("modelId", "claude-3-7-sonnet-20250219") + }) +}) diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts index 45a34bda4e2..d3ea8bfb5f2 100644 --- a/src/services/telemetry/TelemetryService.ts +++ b/src/services/telemetry/TelemetryService.ts @@ -22,6 +22,9 @@ class PostHogClient { CONVERSATION_MESSAGE: "Conversation Message", MODE_SWITCH: "Mode Switched", TOOL_USED: "Tool Used", + CHECKPOINT_CREATED: "Checkpoint Created", + CHECKPOINT_RESTORED: "Checkpoint Restored", + CHECKPOINT_DIFFED: "Checkpoint Diffed", }, } @@ -246,6 +249,18 @@ class TelemetryService { }) } + public captureCheckpointCreated(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_CREATED, { taskId }) + } + + public captureCheckpointDiffed(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_DIFFED, { taskId }) + } + + public captureCheckpointRestored(taskId: string): void { + this.captureEvent(PostHogClient.EVENTS.TASK.CHECKPOINT_RESTORED, { taskId }) + } + /** * Checks if telemetry is currently enabled * @returns Whether telemetry is enabled