{title}
diff --git a/apps/web-roo-code/src/app/legal/cookies/page.tsx b/apps/web-roo-code/src/app/legal/cookies/page.tsx
index 895d7c2b456..bc59878c3c3 100644
--- a/apps/web-roo-code/src/app/legal/cookies/page.tsx
+++ b/apps/web-roo-code/src/app/legal/cookies/page.tsx
@@ -90,7 +90,7 @@ export default function CookiePolicy() {
- | PostHog |
+ Analytics |
Product analytics and feature usage tracking
|
@@ -126,15 +126,6 @@ export default function CookiePolicy() {
Clerk Privacy Policy
-
-
- PostHog Privacy Policy
-
-
Analytics cookies
- We use PostHog and HubSpot analytics cookies to understand how visitors interact with our
- website. This helps us improve our services, user experience, and marketing efforts. Analytics
- cookies are placed only if you give consent through our cookie banner. The lawful basis for
- processing these cookies is your consent, which you can withdraw at any time.
+ We use HubSpot analytics cookies to understand how visitors interact with our website. This
+ helps us improve our services, user experience, and marketing efforts. Analytics cookies are
+ placed only if you give consent through our cookie banner. The lawful basis for processing these
+ cookies is your consent, which you can withdraw at any time.
Third-party services
diff --git a/apps/web-roo-code/src/app/legal/subprocessors/page.tsx b/apps/web-roo-code/src/app/legal/subprocessors/page.tsx
index e78fa407201..fc2c8a1ebac 100644
--- a/apps/web-roo-code/src/app/legal/subprocessors/page.tsx
+++ b/apps/web-roo-code/src/app/legal/subprocessors/page.tsx
@@ -137,7 +137,7 @@ export default function SubProcessors() {
- | PostHog |
+ Analytics |
Data Services |
United States |
Product analytics |
diff --git a/apps/web-roo-code/src/app/privacy/page.tsx b/apps/web-roo-code/src/app/privacy/page.tsx
index e34dffd7f2e..e03c7e6b417 100644
--- a/apps/web-roo-code/src/app/privacy/page.tsx
+++ b/apps/web-roo-code/src/app/privacy/page.tsx
@@ -143,9 +143,9 @@ export default function Privacy() {
| Usage Data |
- Feature clicks, error logs, performance metrics (captured via PostHog)
+ Feature clicks, error logs, performance metrics
|
- Services automatically (PostHog) |
+ Services automatically |
| Payment Data |
@@ -241,9 +241,7 @@ export default function Privacy() {
| Usage & Telemetry |
-
- PostHog (self‑hosted analytics platform)
- |
+ None |
Ad networks or data brokers |
diff --git a/apps/web-roo-code/src/components/blog/BlogAnalytics.tsx b/apps/web-roo-code/src/components/blog/BlogAnalytics.tsx
deleted file mode 100644
index a393d035109..00000000000
--- a/apps/web-roo-code/src/components/blog/BlogAnalytics.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-"use client"
-
-/**
- * Client-side blog analytics components
- * MKT-74: Blog Analytics (PostHog)
- */
-
-import { useEffect } from "react"
-import { trackBlogIndexView, trackBlogPostView } from "@/lib/blog/analytics"
-import type { BlogPost } from "@/lib/blog/types"
-
-/**
- * Tracks blog index page view
- * Place this component on the blog index page
- */
-export function BlogIndexAnalytics() {
- useEffect(() => {
- trackBlogIndexView()
- }, [])
-
- return null
-}
-
-/**
- * Tracks individual blog post view
- * Place this component on blog post pages
- */
-export function BlogPostAnalytics({ post }: { post: BlogPost }) {
- useEffect(() => {
- trackBlogPostView(post)
- }, [post])
-
- return null
-}
-
-/**
- * Serializable post data for client component
- * Use this type when passing post data to BlogPostAnalytics
- */
-export interface SerializablePostData {
- slug: string
- title: string
- publish_date: string
- publish_time_pt: string
- tags: string[]
- status: "draft" | "published"
- description: string
- content: string
- filepath: string
-}
diff --git a/apps/web-roo-code/src/components/providers/posthog-provider.tsx b/apps/web-roo-code/src/components/providers/posthog-provider.tsx
deleted file mode 100644
index 0237fc1665c..00000000000
--- a/apps/web-roo-code/src/components/providers/posthog-provider.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-"use client"
-
-import { usePathname, useSearchParams } from "next/navigation"
-import posthog from "posthog-js"
-import { PostHogProvider as OriginalPostHogProvider } from "posthog-js/react"
-import { useEffect, useRef, Suspense } from "react"
-import { hasConsent } from "@/lib/analytics/consent-manager"
-
-function PageViewTracker() {
- const pathname = usePathname()
- const searchParams = useSearchParams()
- const previousUrl = useRef(null)
-
- // Track page views with proper referrer for SPA navigations
- useEffect(() => {
- if (pathname && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
- const searchString = searchParams?.toString() ?? ""
- const currentUrl = window.location.origin + pathname + (searchString ? `?${searchString}` : "")
-
- // Get referrer - for SPA navigations, use previous URL; otherwise use document.referrer
- const referrer = previousUrl.current ?? document.referrer
- let referringDomain = ""
-
- if (referrer) {
- try {
- referringDomain = new URL(referrer).hostname
- } catch {
- // Invalid URL, leave empty
- }
- }
-
- posthog.capture("$pageview", {
- $current_url: currentUrl,
- $referrer: referrer,
- $referring_domain: referringDomain,
- })
-
- // Update previous URL for next navigation
- previousUrl.current = currentUrl
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [pathname, searchParams?.toString()])
-
- return null
-}
-
-export function PostHogProvider({ children }: { children: React.ReactNode }) {
- useEffect(() => {
- // Initialize PostHog immediately on the client side
- if (typeof window !== "undefined" && !posthog.__loaded) {
- const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY
-
- // Check if environment variables are set
- if (!posthogKey) {
- console.warn(
- "PostHog API key is missing. Analytics will be disabled. " +
- "Please set NEXT_PUBLIC_POSTHOG_KEY in your .env file.",
- )
- return
- }
-
- // Check if user has already consented to cookies
- const userHasConsented = hasConsent()
-
- // Initialize PostHog with appropriate persistence based on consent
- posthog.init(posthogKey, {
- api_host: "https://ph.roocode.com",
- ui_host: "https://us.posthog.com",
- capture_pageview: false, // We handle pageview tracking manually
- loaded: (posthogInstance) => {
- if (process.env.NODE_ENV === "development") {
- posthogInstance.debug()
- }
- },
- save_referrer: true, // Save referrer information
- save_campaign_params: true, // Save UTM parameters
- respect_dnt: true, // Respect Do Not Track
- persistence: userHasConsented ? "localStorage+cookie" : "memory", // Use localStorage if consented, otherwise memory-only
- opt_out_capturing_by_default: false, // Start tracking immediately
- })
- }
- }, [])
-
- return (
-
-
-
-
- {children}
-
- )
-}
diff --git a/apps/web-roo-code/src/components/providers/providers.tsx b/apps/web-roo-code/src/components/providers/providers.tsx
index f97c6921456..fa48211421a 100644
--- a/apps/web-roo-code/src/components/providers/providers.tsx
+++ b/apps/web-roo-code/src/components/providers/providers.tsx
@@ -5,7 +5,6 @@ import { ThemeProvider } from "next-themes"
import { GoogleTagManagerProvider } from "./google-tag-manager-provider"
import { HubSpotProvider } from "./hubspot-provider"
-import { PostHogProvider } from "./posthog-provider"
const queryClient = new QueryClient()
@@ -14,11 +13,9 @@ export const Providers = ({ children }: { children: React.ReactNode }) => {
-
-
- {children}
-
-
+
+ {children}
+
diff --git a/apps/web-roo-code/src/lib/analytics/consent-manager.ts b/apps/web-roo-code/src/lib/analytics/consent-manager.ts
index a99f6c19f2f..0a3e67d8b3b 100644
--- a/apps/web-roo-code/src/lib/analytics/consent-manager.ts
+++ b/apps/web-roo-code/src/lib/analytics/consent-manager.ts
@@ -1,72 +1,42 @@
-/**
- * Simple consent event system
- * Dispatches events when cookie consent changes
- */
+const CONSENT_EVENT = "roo-cookie-consent-change"
-import { getCookieConsentValue } from "react-cookie-consent"
-import { CONSENT_COOKIE_NAME } from "@roo-code/types"
-import posthog from "posthog-js"
+type ConsentListener = (consented: boolean) => void
-export const CONSENT_EVENT = "cookieConsentChanged"
-
-/**
- * Check if user has given consent for analytics cookies
- * Uses react-cookie-consent's built-in function
- */
export function hasConsent(): boolean {
- if (typeof window === "undefined") return false
- return getCookieConsentValue(CONSENT_COOKIE_NAME) === "true"
+ if (typeof window === "undefined") {
+ return false
+ }
+
+ return window.localStorage.getItem("roo-cookie-consent") === "accepted"
+}
+
+export function handleConsentAccept(): void {
+ if (typeof window === "undefined") {
+ return
+ }
+
+ window.localStorage.setItem("roo-cookie-consent", "accepted")
+ window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: true }))
}
-/**
- * Dispatch a consent change event
- */
-export function dispatchConsentEvent(consented: boolean): void {
- if (typeof window !== "undefined") {
- const event = new CustomEvent(CONSENT_EVENT, {
- detail: { consented },
- })
- window.dispatchEvent(event)
+export function handleConsentReject(): void {
+ if (typeof window === "undefined") {
+ return
}
+
+ window.localStorage.setItem("roo-cookie-consent", "rejected")
+ window.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: false }))
}
-/**
- * Listen for consent changes
- */
-export function onConsentChange(callback: (consented: boolean) => void): () => void {
+export function onConsentChange(listener: ConsentListener): () => void {
if (typeof window === "undefined") {
return () => {}
}
const handler = (event: Event) => {
- const customEvent = event as CustomEvent<{ consented: boolean }>
- callback(customEvent.detail.consented)
+ listener(Boolean((event as CustomEvent).detail))
}
window.addEventListener(CONSENT_EVENT, handler)
return () => window.removeEventListener(CONSENT_EVENT, handler)
}
-
-/**
- * Handle user accepting cookies
- * Opts PostHog back into cookie-based tracking
- */
-export function handleConsentAccept(): void {
- if (typeof window !== "undefined" && posthog.__loaded) {
- // User accepted - ensure localStorage+cookie persistence is enabled
- posthog.opt_in_capturing()
- posthog.set_config({
- persistence: "localStorage+cookie",
- })
- }
- dispatchConsentEvent(true)
-}
-
-/**
- * Handle user rejecting cookies
- * Switches PostHog to cookieless (memory-only) mode
- */
-export function handleConsentReject(): void {
- // User rejected - stick to cookieless mode
- dispatchConsentEvent(false)
-}
diff --git a/apps/web-roo-code/src/lib/blog/analytics.ts b/apps/web-roo-code/src/lib/blog/analytics.ts
deleted file mode 100644
index 886e8198d73..00000000000
--- a/apps/web-roo-code/src/lib/blog/analytics.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Blog-specific PostHog analytics events
- * MKT-74: Blog Analytics (PostHog)
- */
-
-import posthog from "posthog-js"
-import type { BlogPost } from "./types"
-
-/**
- * Track blog index page view
- */
-export function trackBlogIndexView(): void {
- if (typeof window !== "undefined" && posthog.__loaded) {
- posthog.capture("blog_index_view")
- }
-}
-
-/**
- * Track individual blog post view
- */
-export function trackBlogPostView(post: BlogPost): void {
- if (typeof window !== "undefined" && posthog.__loaded) {
- posthog.capture("blog_post_view", {
- slug: post.slug,
- title: post.title,
- publish_date: post.publish_date,
- publish_time_pt: post.publish_time_pt,
- tags: post.tags,
- })
- }
-}
-
-/**
- * Track Substack subscribe click
- */
-export function trackSubstackClick(): void {
- if (typeof window !== "undefined" && posthog.__loaded) {
- posthog.capture("blog_substack_click")
- }
-}
diff --git a/apps/web-roo-code/src/lib/blog/index.ts b/apps/web-roo-code/src/lib/blog/index.ts
index 0d518396c44..a84a9bf2af9 100644
--- a/apps/web-roo-code/src/lib/blog/index.ts
+++ b/apps/web-roo-code/src/lib/blog/index.ts
@@ -34,4 +34,3 @@ export {
export { BlogFrontmatterSchema, type ValidatedFrontmatter } from "./validation"
// Analytics
-export { trackBlogIndexView, trackBlogPostView, trackSubstackClick } from "./analytics"
diff --git a/knip.json b/knip.json
index 203affa88c8..66240fe7bcd 100644
--- a/knip.json
+++ b/knip.json
@@ -20,7 +20,7 @@
"entry": ["src/index.tsx"],
"project": ["src/**/*.{ts,tsx}", "../src/shared/*.ts"]
},
- "packages/{build,cloud,evals,ipc,telemetry,types}": {
+ "packages/{build,cloud,evals,ipc,types}": {
"project": ["src/**/*.ts"]
}
}
diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts
index 43f52d4b18a..809f5805d57 100644
--- a/packages/cloud/src/CloudService.ts
+++ b/packages/cloud/src/CloudService.ts
@@ -2,7 +2,6 @@ import type { Disposable, ExtensionContext } from "vscode"
import EventEmitter from "events"
import type {
- TelemetryEvent,
ClineMessage,
CloudServiceEvents,
AuthService,
@@ -22,7 +21,6 @@ import { WebAuthService } from "./WebAuthService.js"
import { StaticTokenAuthService } from "./StaticTokenAuthService.js"
import { CloudSettingsService } from "./CloudSettingsService.js"
import { StaticSettingsService } from "./StaticSettingsService.js"
-import { CloudTelemetryClient as TelemetryClient } from "./TelemetryClient.js"
import { CloudShareService } from "./CloudShareService.js"
import { CloudAPI } from "./CloudAPI.js"
import { RetryQueue } from "./retry-queue/index.js"
@@ -59,12 +57,6 @@ export class CloudService extends EventEmitter implements Di
return this._settingsService
}
- private _telemetryClient: TelemetryClient | null = null
-
- public get telemetryClient() {
- return this._telemetryClient
- }
-
private _shareService: CloudShareService | null = null
public get shareService() {
@@ -165,8 +157,6 @@ export class CloudService extends EventEmitter implements Di
},
)
- this._telemetryClient = new TelemetryClient(this._authService, this._settingsService, this._retryQueue)
-
this._shareService = new CloudShareService(this._cloudAPI, this._settingsService, this.log)
this.isInitialized = true
@@ -303,13 +293,6 @@ export class CloudService extends EventEmitter implements Di
return this.settingsService!.isTaskSyncEnabled()
}
- // TelemetryClient
-
- public captureEvent(event: TelemetryEvent): void {
- this.ensureInitialized()
- this.telemetryClient!.capture(event)
- }
-
// ShareService
public async shareTask(
@@ -323,9 +306,7 @@ export class CloudService extends EventEmitter implements Di
return await this.shareService!.shareTask(taskId, visibility)
} catch (error) {
if (error instanceof TaskNotFoundError && clineMessages) {
- // Backfill messages and retry.
- await this.telemetryClient!.backfillMessages(clineMessages, taskId)
- return await this.shareService!.shareTask(taskId, visibility)
+ this.log(`[CloudService] Task ${taskId} was not found while sharing ${clineMessages.length} messages`)
}
throw error
diff --git a/packages/cloud/src/TelemetryClient.ts b/packages/cloud/src/TelemetryClient.ts
deleted file mode 100644
index 252cc8ad42b..00000000000
--- a/packages/cloud/src/TelemetryClient.ts
+++ /dev/null
@@ -1,290 +0,0 @@
-import {
- type TelemetryClient,
- type TelemetryEvent,
- type ClineMessage,
- type AuthService,
- type SettingsService,
- TelemetryEventName,
- rooCodeTelemetryEventSchema,
- TelemetryPropertiesProvider,
- TelemetryEventSubscription,
-} from "@roo-code/types"
-
-import { getRooCodeApiUrl } from "./config.js"
-import type { RetryQueue } from "./retry-queue/index.js"
-
-abstract class BaseTelemetryClient implements TelemetryClient {
- protected providerRef: WeakRef | null = null
- protected telemetryEnabled: boolean = false
-
- constructor(
- public readonly subscription?: TelemetryEventSubscription,
- protected readonly debug = false,
- ) {}
-
- protected isEventCapturable(eventName: TelemetryEventName): boolean {
- if (!this.subscription) {
- return true
- }
-
- return this.subscription.type === "include"
- ? this.subscription.events.includes(eventName)
- : !this.subscription.events.includes(eventName)
- }
-
- /**
- * Determines if a specific property should be included in telemetry events
- * Override in subclasses to filter specific properties
- */
- protected isPropertyCapturable(_propertyName: string): boolean {
- return true
- }
-
- protected async getEventProperties(event: TelemetryEvent): Promise {
- let providerProperties: TelemetryEvent["properties"] = {}
- const provider = this.providerRef?.deref()
-
- if (provider) {
- try {
- // Get properties from the provider
- providerProperties = await provider.getTelemetryProperties()
- } catch (error) {
- // Log error but continue with capturing the event.
- console.error(
- `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- }
-
- // Merge provider properties with event-specific properties.
- // Event properties take precedence in case of conflicts.
- const mergedProperties = {
- ...providerProperties,
- ...(event.properties || {}),
- }
-
- // Filter out properties that shouldn't be captured by this client
- return Object.fromEntries(Object.entries(mergedProperties).filter(([key]) => this.isPropertyCapturable(key)))
- }
-
- public abstract capture(event: TelemetryEvent): Promise
-
- public async captureException(_error: Error, _additionalProperties?: Record): Promise {}
-
- public setProvider(provider: TelemetryPropertiesProvider): void {
- this.providerRef = new WeakRef(provider)
- }
-
- public abstract updateTelemetryState(didUserOptIn: boolean): void
-
- public isTelemetryEnabled(): boolean {
- return this.telemetryEnabled
- }
-
- public abstract shutdown(): Promise
-}
-
-export class CloudTelemetryClient extends BaseTelemetryClient {
- private retryQueue: RetryQueue | null = null
-
- constructor(
- private authService: AuthService,
- private settingsService: SettingsService,
- retryQueue?: RetryQueue,
- ) {
- super({
- type: "exclude",
- events: [TelemetryEventName.TASK_CONVERSATION_MESSAGE],
- })
- this.retryQueue = retryQueue || null
- }
-
- private async fetch(path: string, options: RequestInit, allowQueueing = true) {
- if (!this.authService.isAuthenticated()) {
- return
- }
-
- const token = this.authService.getSessionToken()
-
- if (!token) {
- console.error(`[TelemetryClient#fetch] Unauthorized: No session token available.`)
- return
- }
-
- const url = `${getRooCodeApiUrl()}/api/${path}`
- const fetchOptions: RequestInit = {
- ...options,
- headers: {
- Authorization: `Bearer ${token}`,
- "Content-Type": "application/json",
- },
- }
-
- try {
- const response = await fetch(url, fetchOptions)
-
- if (!response.ok) {
- console.error(
- `[TelemetryClient#fetch] ${options.method} ${path} -> ${response.status} ${response.statusText}`,
- )
-
- // Queue for retry on server errors (5xx) or rate limiting (429)
- // Do NOT retry on client errors (4xx) except 429 - they won't succeed
- if (this.retryQueue && allowQueueing && (response.status >= 500 || response.status === 429)) {
- await this.retryQueue.enqueue(url, fetchOptions, "telemetry")
- }
- }
-
- return response
- } catch (error) {
- console.error(`[TelemetryClient#fetch] Network error for ${options.method} ${path}: ${error}`)
-
- // Queue for retry on network failures (typically TypeError with "fetch failed" message)
- // These are transient network issues that may succeed on retry
- if (
- this.retryQueue &&
- allowQueueing &&
- error instanceof TypeError &&
- error.message.includes("fetch failed")
- ) {
- await this.retryQueue.enqueue(url, fetchOptions, "telemetry")
- }
-
- throw error
- }
- }
-
- public override async capture(event: TelemetryEvent) {
- if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
- if (this.debug) {
- console.info(`[TelemetryClient#capture] Skipping event: ${event.event}`)
- }
-
- return
- }
-
- const payload = {
- type: event.event,
- properties: await this.getEventProperties(event),
- }
-
- if (this.debug) {
- console.info(`[TelemetryClient#capture] ${JSON.stringify(payload)}`)
- }
-
- const result = rooCodeTelemetryEventSchema.safeParse(payload)
-
- if (!result.success) {
- console.error(
- `[TelemetryClient#capture] Invalid telemetry event: ${result.error.message} - ${JSON.stringify(payload)}`,
- )
-
- return
- }
-
- try {
- await this.fetch(`events`, {
- method: "POST",
- body: JSON.stringify(result.data),
- })
- } catch (error) {
- console.error(`[TelemetryClient#capture] Error sending telemetry event: ${error}`)
- // Error is already queued for retry in the fetch method
- }
- }
-
- public async backfillMessages(messages: ClineMessage[], taskId: string): Promise {
- if (!this.isTelemetryEnabled()) {
- return
- }
-
- if (!this.authService.isAuthenticated()) {
- if (this.debug) {
- console.info(`[TelemetryClient#backfillMessages] Skipping: Not authenticated`)
- }
- return
- }
-
- const token = this.authService.getSessionToken()
-
- if (!token) {
- console.error(`[TelemetryClient#backfillMessages] Unauthorized: No session token available.`)
- return
- }
-
- try {
- const mergedProperties = await this.getEventProperties({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: { taskId },
- })
-
- const formData = new FormData()
- formData.append("taskId", taskId)
- formData.append("properties", JSON.stringify(mergedProperties))
-
- formData.append(
- "file",
- new File([JSON.stringify(messages)], "task.json", {
- type: "application/json",
- }),
- )
-
- if (this.debug) {
- console.info(
- `[TelemetryClient#backfillMessages] Uploading ${messages.length} messages for task ${taskId}`,
- )
- }
-
- const url = `${getRooCodeApiUrl()}/api/events/backfill`
- const fetchOptions: RequestInit = {
- method: "POST",
- headers: {
- Authorization: `Bearer ${token}`,
- },
- body: formData,
- }
-
- try {
- const response = await fetch(url, fetchOptions)
-
- if (!response.ok) {
- console.error(
- `[TelemetryClient#backfillMessages] POST events/backfill -> ${response.status} ${response.statusText}`,
- )
- }
- } catch (fetchError) {
- console.error(`[TelemetryClient#backfillMessages] Network error: ${fetchError}`)
- throw fetchError
- }
- } catch (error) {
- console.error(`[TelemetryClient#backfillMessages] Error uploading messages: ${error}`)
- }
- }
-
- public override updateTelemetryState(_didUserOptIn: boolean) {}
-
- public override isTelemetryEnabled(): boolean {
- if (process.env.ROO_CODE_DISABLE_TELEMETRY === "1") {
- return false
- }
-
- return true
- }
-
- protected override isEventCapturable(eventName: TelemetryEventName): boolean {
- // Ensure that this event type is supported by the telemetry client
- if (!super.isEventCapturable(eventName)) {
- return false
- }
-
- // Only record message telemetry if task sync is enabled
- if (eventName === TelemetryEventName.TASK_MESSAGE) {
- return this.settingsService.isTaskSyncEnabled()
- }
-
- // Other telemetry types are capturable at this point
- return true
- }
-
- public override async shutdown() {}
-}
diff --git a/packages/cloud/src/__tests__/CloudService.test.ts b/packages/cloud/src/__tests__/CloudService.test.ts
index 8c557ae7ada..51aac12a2a7 100644
--- a/packages/cloud/src/__tests__/CloudService.test.ts
+++ b/packages/cloud/src/__tests__/CloudService.test.ts
@@ -9,7 +9,6 @@ import { CloudService } from "../CloudService.js"
import { WebAuthService } from "../WebAuthService.js"
import { CloudSettingsService } from "../CloudSettingsService.js"
import { CloudShareService } from "../CloudShareService.js"
-import { CloudTelemetryClient as TelemetryClient } from "../TelemetryClient.js"
vi.mock("vscode", () => ({
ExtensionContext: vi.fn(),
@@ -31,8 +30,6 @@ vi.mock("../CloudSettingsService")
vi.mock("../CloudShareService")
-vi.mock("../TelemetryClient")
-
describe("CloudService", () => {
let mockContext: vscode.ExtensionContext
@@ -70,10 +67,6 @@ describe("CloudService", () => {
canShareTask: ReturnType
}
- let mockTelemetryClient: {
- backfillMessages: ReturnType
- }
-
beforeEach(() => {
CloudService.resetInstance()
@@ -142,17 +135,11 @@ describe("CloudService", () => {
canShareTask: vi.fn().mockResolvedValue(true),
}
- mockTelemetryClient = {
- backfillMessages: vi.fn().mockResolvedValue(undefined),
- }
-
vi.mocked(WebAuthService).mockImplementation(() => mockAuthService as unknown as WebAuthService)
vi.mocked(CloudSettingsService).mockImplementation(() => mockSettingsService as unknown as CloudSettingsService)
vi.mocked(CloudShareService).mockImplementation(() => mockShareService as unknown as CloudShareService)
-
- vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
})
afterEach(() => {
@@ -530,11 +517,10 @@ describe("CloudService", () => {
expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
expect(mockShareService.shareTask).toHaveBeenCalledWith(taskId, visibility)
- expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
expect(result).toEqual(expectedResult)
})
- it("should retry with backfill when TaskNotFoundError occurs", async () => {
+ it("should throw when TaskNotFoundError occurs", async () => {
const taskId = "test-task-id"
const visibility = "organization"
const clineMessages: ClineMessage[] = [
@@ -546,24 +532,12 @@ describe("CloudService", () => {
},
]
- const expectedResult = {
- success: true,
- shareUrl: "https://example.com/share/123",
- }
+ mockShareService.shareTask.mockRejectedValueOnce(new TaskNotFoundError(taskId))
- // First call throws TaskNotFoundError, second call succeeds
- mockShareService.shareTask
- .mockRejectedValueOnce(new TaskNotFoundError(taskId))
- .mockResolvedValueOnce(expectedResult)
-
- const result = await cloudService.shareTask(taskId, visibility, clineMessages)
+ await expect(cloudService.shareTask(taskId, visibility, clineMessages)).rejects.toThrow(TaskNotFoundError)
- expect(mockShareService.shareTask).toHaveBeenCalledTimes(2)
- expect(mockShareService.shareTask).toHaveBeenNthCalledWith(1, taskId, visibility)
- expect(mockShareService.shareTask).toHaveBeenNthCalledWith(2, taskId, visibility)
- expect(mockTelemetryClient.backfillMessages).toHaveBeenCalledTimes(1)
- expect(mockTelemetryClient.backfillMessages).toHaveBeenCalledWith(clineMessages, taskId)
- expect(result).toEqual(expectedResult)
+ expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
+ expect(mockShareService.shareTask).toHaveBeenCalledWith(taskId, visibility)
})
it("should not retry when TaskNotFoundError occurs but no clineMessages provided", async () => {
@@ -576,7 +550,6 @@ describe("CloudService", () => {
await expect(cloudService.shareTask(taskId, visibility)).rejects.toThrow(TaskNotFoundError)
expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
- expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
})
it("should not retry when non-TaskNotFoundError occurs", async () => {
@@ -597,7 +570,6 @@ describe("CloudService", () => {
await expect(cloudService.shareTask(taskId, visibility, clineMessages)).rejects.toThrow(genericError)
expect(mockShareService.shareTask).toHaveBeenCalledTimes(1)
- expect(mockTelemetryClient.backfillMessages).not.toHaveBeenCalled()
})
it("should work with default parameters", async () => {
diff --git a/packages/cloud/src/__tests__/TelemetryClient.test.ts b/packages/cloud/src/__tests__/TelemetryClient.test.ts
deleted file mode 100644
index 5cb952dc280..00000000000
--- a/packages/cloud/src/__tests__/TelemetryClient.test.ts
+++ /dev/null
@@ -1,675 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-// npx vitest run src/__tests__/TelemetryClient.test.ts
-
-import { type TelemetryPropertiesProvider, TelemetryEventName } from "@roo-code/types"
-
-import { CloudTelemetryClient as TelemetryClient } from "../TelemetryClient.js"
-
-const mockFetch = vi.fn()
-global.fetch = mockFetch as any
-
-describe("TelemetryClient", () => {
- const getPrivateProperty = (instance: any, propertyName: string): T => {
- return instance[propertyName]
- }
-
- let mockAuthService: any
- let mockSettingsService: any
-
- beforeEach(() => {
- vi.clearAllMocks()
-
- // Create a mock AuthService instead of using the singleton
- mockAuthService = {
- getSessionToken: vi.fn().mockReturnValue("mock-token"),
- getState: vi.fn().mockReturnValue("active-session"),
- isAuthenticated: vi.fn().mockReturnValue(true),
- hasActiveSession: vi.fn().mockReturnValue(true),
- }
-
- // Create a mock SettingsService
- mockSettingsService = {
- getSettings: vi.fn().mockReturnValue({
- cloudSettings: {
- recordTaskMessages: true,
- },
- }),
- getUserSettings: vi.fn().mockReturnValue({
- features: {},
- settings: {
- taskSyncEnabled: true,
- },
- version: 1,
- }),
- isTaskSyncEnabled: vi.fn().mockReturnValue(true),
- }
-
- mockFetch.mockResolvedValue({
- ok: true,
- json: vi.fn().mockResolvedValue({}),
- })
-
- vi.spyOn(console, "info").mockImplementation(() => {})
- vi.spyOn(console, "error").mockImplementation(() => {})
- })
-
- afterEach(() => {
- vi.restoreAllMocks()
- })
-
- describe("isEventCapturable", () => {
- it("should return true for events not in exclude list", () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.TASK_CREATED)).toBe(true)
- expect(isEventCapturable(TelemetryEventName.LLM_COMPLETION)).toBe(true)
- expect(isEventCapturable(TelemetryEventName.MODE_SWITCH)).toBe(true)
- expect(isEventCapturable(TelemetryEventName.TOOL_USED)).toBe(true)
- })
-
- it("should return false for events in exclude list", () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.TASK_CONVERSATION_MESSAGE)).toBe(false)
- })
-
- it("should return true for TASK_MESSAGE events when isTaskSyncEnabled returns true", () => {
- mockSettingsService.isTaskSyncEnabled.mockReturnValue(true)
-
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(true)
- expect(mockSettingsService.isTaskSyncEnabled).toHaveBeenCalled()
- })
-
- it("should return false for TASK_MESSAGE events when isTaskSyncEnabled returns false", () => {
- mockSettingsService.isTaskSyncEnabled.mockReturnValue(false)
-
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.TASK_MESSAGE)).toBe(false)
- expect(mockSettingsService.isTaskSyncEnabled).toHaveBeenCalled()
- })
- })
-
- describe("getEventProperties", () => {
- it("should merge provider properties with event properties", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }),
- }
-
- client.setProvider(mockProvider)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: {
- customProp: "value",
- mode: "override", // This should override the provider's mode.
- },
- })
-
- expect(result).toEqual({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "override", // Event property takes precedence.
- customProp: "value",
- })
-
- expect(mockProvider.getTelemetryProperties).toHaveBeenCalledTimes(1)
- })
-
- it("should handle errors from provider gracefully", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
- }
-
- const consoleErrorSpy = vi.spyOn(console, "error")
-
- client.setProvider(mockProvider)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: { customProp: "value" },
- })
-
- expect(result).toEqual({ customProp: "value" })
- expect(consoleErrorSpy).toHaveBeenCalledWith(
- expect.stringContaining("Error getting telemetry properties: Provider error"),
- )
- })
-
- it("should return event properties when no provider is set", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: { customProp: "value" },
- })
-
- expect(result).toEqual({ customProp: "value" })
- })
- })
-
- describe("capture", () => {
- it("should not capture events that are not capturable", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.capture({
- event: TelemetryEventName.TASK_CONVERSATION_MESSAGE, // In exclude list.
- properties: { test: "value" },
- })
-
- expect(mockFetch).not.toHaveBeenCalled()
- })
-
- it("should not capture TASK_MESSAGE events when recordTaskMessages is false", async () => {
- mockSettingsService.getSettings.mockReturnValue({
- cloudSettings: {
- recordTaskMessages: false,
- },
- })
-
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.capture({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: {
- taskId: "test-task-id",
- message: {
- ts: 1,
- type: "say",
- say: "text",
- text: "test message",
- },
- },
- })
-
- expect(mockFetch).not.toHaveBeenCalled()
- })
-
- it("should not capture TASK_MESSAGE events when isTaskSyncEnabled returns false", async () => {
- mockSettingsService.isTaskSyncEnabled.mockReturnValue(false)
-
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.capture({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: {
- taskId: "test-task-id",
- message: {
- ts: 1,
- type: "say",
- say: "text",
- text: "test message",
- },
- },
- })
-
- expect(mockFetch).not.toHaveBeenCalled()
- expect(mockSettingsService.isTaskSyncEnabled).toHaveBeenCalled()
- })
-
- it("should not send request when schema validation fails", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: { test: "value" },
- })
-
- expect(mockFetch).not.toHaveBeenCalled()
- expect(console.error).toHaveBeenCalledWith(expect.stringContaining("Invalid telemetry event"))
- })
-
- it("should send request when event is capturable and validation passes", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const providerProperties = {
- appName: "roo-code",
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }
-
- const eventProperties = {
- taskId: "test-task-id",
- }
-
- const mockValidatedData = {
- type: TelemetryEventName.TASK_CREATED,
- properties: {
- ...providerProperties,
- taskId: "test-task-id",
- },
- }
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue(providerProperties),
- }
-
- client.setProvider(mockProvider)
-
- await client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: eventProperties,
- })
-
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events",
- expect.objectContaining({
- method: "POST",
- body: JSON.stringify(mockValidatedData),
- }),
- )
- })
-
- it("should attempt to capture TASK_MESSAGE events when isTaskSyncEnabled returns true", async () => {
- mockSettingsService.isTaskSyncEnabled.mockReturnValue(true)
-
- const eventProperties = {
- appName: "roo-code",
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- taskId: "test-task-id",
- message: {
- ts: 1,
- type: "say",
- say: "text",
- text: "test message",
- },
- }
-
- const mockValidatedData = {
- type: TelemetryEventName.TASK_MESSAGE,
- properties: eventProperties,
- }
-
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.capture({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: eventProperties,
- })
-
- expect(mockSettingsService.isTaskSyncEnabled).toHaveBeenCalled()
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events",
- expect.objectContaining({
- method: "POST",
- body: JSON.stringify(mockValidatedData),
- }),
- )
- })
-
- it("should handle fetch errors gracefully", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- mockFetch.mockRejectedValue(new Error("Network error"))
-
- await expect(
- client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: { test: "value" },
- }),
- ).resolves.not.toThrow()
- })
- })
-
- describe("telemetry state methods", () => {
- it("should always return true for isTelemetryEnabled", () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
- expect(client.isTelemetryEnabled()).toBe(true)
- })
-
- it("should have empty implementations for updateTelemetryState and shutdown", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
- client.updateTelemetryState(true)
- await client.shutdown()
- })
- })
-
- describe("backfillMessages", () => {
- it("should not send request when not authenticated", async () => {
- mockAuthService.isAuthenticated.mockReturnValue(false)
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(mockFetch).not.toHaveBeenCalled()
- })
-
- it("should not send request when no session token available", async () => {
- mockAuthService.getSessionToken.mockReturnValue(null)
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(mockFetch).not.toHaveBeenCalled()
- expect(console.error).toHaveBeenCalledWith(
- "[TelemetryClient#backfillMessages] Unauthorized: No session token available.",
- )
- })
-
- it("should send FormData request with correct structure when authenticated", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const providerProperties = {
- appName: "roo-code",
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue(providerProperties),
- }
-
- client.setProvider(mockProvider)
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message 1",
- },
- {
- ts: 2,
- type: "ask" as const,
- ask: "followup" as const,
- text: "test question",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events/backfill",
- expect.objectContaining({
- method: "POST",
- headers: {
- Authorization: "Bearer mock-token",
- },
- body: expect.any(FormData),
- }),
- )
-
- // Verify FormData contents
- const call = mockFetch.mock.calls[0]
- const formData = call?.[1]?.body as FormData
-
- expect(formData.get("taskId")).toBe("test-task-id")
-
- // Parse and compare properties as objects since JSON.stringify order can vary
- const propertiesJson = formData.get("properties") as string
- const parsedProperties = JSON.parse(propertiesJson)
- expect(parsedProperties).toEqual({
- taskId: "test-task-id",
- ...providerProperties,
- })
- // The messages are stored as a File object under the "file" key
- const fileField = formData.get("file") as File
- expect(fileField).toBeInstanceOf(File)
- expect(fileField.name).toBe("task.json")
- expect(fileField.type).toBe("application/json")
-
- // Read the file content to verify the messages
- const fileContent = await fileField.text()
- expect(fileContent).toBe(JSON.stringify(messages))
- })
-
- it("should handle provider errors gracefully", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
- }
-
- client.setProvider(mockProvider)
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events/backfill",
- expect.objectContaining({
- method: "POST",
- headers: {
- Authorization: "Bearer mock-token",
- },
- body: expect.any(FormData),
- }),
- )
-
- // Verify FormData contents - should still work with just taskId
- const call = mockFetch.mock.calls[0]
- const formData = call?.[1]?.body as FormData
-
- expect(formData.get("taskId")).toBe("test-task-id")
- expect(formData.get("properties")).toBe(
- JSON.stringify({
- taskId: "test-task-id",
- }),
- )
-
- // The messages are stored as a File object under the "file" key
- const fileField = formData.get("file") as File
- expect(fileField).toBeInstanceOf(File)
- expect(fileField.name).toBe("task.json")
- expect(fileField.type).toBe("application/json")
-
- // Read the file content to verify the messages
- const fileContent = await fileField.text()
- expect(fileContent).toBe(JSON.stringify(messages))
- })
-
- it("should work without provider set", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events/backfill",
- expect.objectContaining({
- method: "POST",
- headers: {
- Authorization: "Bearer mock-token",
- },
- body: expect.any(FormData),
- }),
- )
-
- // Verify FormData contents - should work with just taskId
- const call = mockFetch.mock.calls[0]
- const formData = call?.[1]?.body as FormData
-
- expect(formData.get("taskId")).toBe("test-task-id")
- expect(formData.get("properties")).toBe(
- JSON.stringify({
- taskId: "test-task-id",
- }),
- )
-
- // The messages are stored as a File object under the "file" key
- const fileField = formData.get("file") as File
- expect(fileField).toBeInstanceOf(File)
- expect(fileField.name).toBe("task.json")
- expect(fileField.type).toBe("application/json")
-
- // Read the file content to verify the messages
- const fileContent = await fileField.text()
- expect(fileContent).toBe(JSON.stringify(messages))
- })
-
- it("should handle fetch errors gracefully", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- mockFetch.mockRejectedValue(new Error("Network error"))
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await expect(client.backfillMessages(messages, "test-task-id")).resolves.not.toThrow()
-
- expect(console.error).toHaveBeenCalledWith(
- expect.stringContaining(
- "[TelemetryClient#backfillMessages] Error uploading messages: Error: Network error",
- ),
- )
- })
-
- it("should handle HTTP error responses", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- mockFetch.mockResolvedValue({
- ok: false,
- status: 404,
- statusText: "Not Found",
- })
-
- const messages = [
- {
- ts: 1,
- type: "say" as const,
- say: "text" as const,
- text: "test message",
- },
- ]
-
- await client.backfillMessages(messages, "test-task-id")
-
- expect(console.error).toHaveBeenCalledWith(
- "[TelemetryClient#backfillMessages] POST events/backfill -> 404 Not Found",
- )
- })
-
- it("should handle empty messages array", async () => {
- const client = new TelemetryClient(mockAuthService, mockSettingsService)
-
- await client.backfillMessages([], "test-task-id")
-
- expect(mockFetch).toHaveBeenCalledWith(
- "https://app.roocode.com/api/events/backfill",
- expect.objectContaining({
- method: "POST",
- headers: {
- Authorization: "Bearer mock-token",
- },
- body: expect.any(FormData),
- }),
- )
-
- // Verify FormData contents
- const call = mockFetch.mock.calls[0]
- const formData = call?.[1]?.body as FormData
-
- // The messages are stored as a File object under the "file" key
- const fileField = formData.get("file") as File
- expect(fileField).toBeInstanceOf(File)
- expect(fileField.name).toBe("task.json")
- expect(fileField.type).toBe("application/json")
-
- // Read the file content to verify the empty messages array
- const fileContent = await fileField.text()
- expect(fileContent).toBe("[]")
- })
- })
-})
diff --git a/packages/cloud/src/retry-queue/__tests__/RetryQueue.test.ts b/packages/cloud/src/retry-queue/__tests__/RetryQueue.test.ts
index becee719c15..d9409faf9d4 100644
--- a/packages/cloud/src/retry-queue/__tests__/RetryQueue.test.ts
+++ b/packages/cloud/src/retry-queue/__tests__/RetryQueue.test.ts
@@ -35,11 +35,11 @@ describe("RetryQueue", () => {
const url = "https://api.example.com/test"
const options = { method: "POST", body: JSON.stringify({ test: "data" }) }
- await retryQueue.enqueue(url, options, "telemetry")
+ await retryQueue.enqueue(url, options, "event")
const stats = retryQueue.getStats()
expect(stats.totalQueued).toBe(1)
- expect(stats.byType["telemetry"]).toBe(1)
+ expect(stats.byType["event"]).toBe(1)
})
it("should enforce max queue size with FIFO eviction", async () => {
@@ -48,7 +48,7 @@ describe("RetryQueue", () => {
// Add 4 requests
for (let i = 1; i <= 4; i++) {
- await retryQueue.enqueue(`https://api.example.com/test${i}`, { method: "POST" }, "telemetry")
+ await retryQueue.enqueue(`https://api.example.com/test${i}`, { method: "POST" }, "event")
}
const stats = retryQueue.getStats()
@@ -58,14 +58,14 @@ describe("RetryQueue", () => {
describe("persistence", () => {
it("should persist queue to workspace state", async () => {
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
expect(mockContext.workspaceState.update).toHaveBeenCalledWith(
"roo.retryQueue",
expect.arrayContaining([
expect.objectContaining({
url: "https://api.example.com/test",
- type: "telemetry",
+ type: "event",
}),
]),
)
@@ -79,7 +79,7 @@ describe("RetryQueue", () => {
options: { method: "POST" },
timestamp: Date.now(),
retryCount: 0,
- type: "telemetry",
+ type: "event",
},
]
@@ -102,7 +102,7 @@ describe("RetryQueue", () => {
describe("clear", () => {
it("should clear all queued requests", async () => {
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "api-call")
let stats = retryQueue.getStats()
@@ -119,14 +119,14 @@ describe("RetryQueue", () => {
it("should return correct statistics", async () => {
const now = Date.now()
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "api-call")
- await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "event")
const stats = retryQueue.getStats()
expect(stats.totalQueued).toBe(3)
- expect(stats.byType["telemetry"]).toBe(2)
+ expect(stats.byType["event"]).toBe(2)
expect(stats.byType["api-call"]).toBe(1)
expect(stats.oldestRequest).toBeDefined()
expect(stats.newestRequest).toBeDefined()
@@ -140,12 +140,12 @@ describe("RetryQueue", () => {
const listener = vi.fn()
retryQueue.on("request-queued", listener)
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://api.example.com/test",
- type: "telemetry",
+ type: "event",
}),
)
})
@@ -175,7 +175,7 @@ describe("RetryQueue", () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true })
global.fetch = fetchMock
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Pause the queue
retryQueue.pause()
@@ -209,8 +209,8 @@ describe("RetryQueue", () => {
it("should clear queue when user changes", async () => {
// Add some requests
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
let stats = retryQueue.getStats()
expect(stats.totalQueued).toBe(2)
@@ -237,8 +237,8 @@ describe("RetryQueue", () => {
retryQueue.setCurrentUserId("user_123")
// Add some requests
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
let stats = retryQueue.getStats()
expect(stats.totalQueued).toBe(2)
@@ -253,8 +253,8 @@ describe("RetryQueue", () => {
it("should not clear on first login (no previous user)", async () => {
// Add some requests before any user is set
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
let stats = retryQueue.getStats()
expect(stats.totalQueued).toBe(2)
@@ -273,7 +273,7 @@ describe("RetryQueue", () => {
// First user logs in
retryQueue.clearIfUserChanged("user_123")
- await retryQueue.enqueue("https://api.example.com/user1-req", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/user1-req", { method: "POST" }, "event")
// User logs out
const clearedOnLogout = retryQueue.clearIfUserChanged(undefined)
@@ -281,13 +281,13 @@ describe("RetryQueue", () => {
expect(clearListener).toHaveBeenCalledTimes(1)
// Different user logs in
- await retryQueue.enqueue("https://api.example.com/user2-req", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/user2-req", { method: "POST" }, "event")
const clearedOnNewUser = retryQueue.clearIfUserChanged("user_456")
expect(clearedOnNewUser).toBe(true)
expect(clearListener).toHaveBeenCalledTimes(2)
// Same user logs back in
- await retryQueue.enqueue("https://api.example.com/user2-req2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/user2-req2", { method: "POST" }, "event")
const notCleared = retryQueue.clearIfUserChanged("user_456")
expect(notCleared).toBe(false)
expect(clearListener).toHaveBeenCalledTimes(2) // Still 2, not cleared
@@ -315,9 +315,9 @@ describe("RetryQueue", () => {
retryQueue.on("request-retry-success", successListener)
// Add multiple requests
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "event")
// Mock successful responses
fetchMock.mockResolvedValue({ ok: true })
@@ -342,7 +342,7 @@ describe("RetryQueue", () => {
const failListener = vi.fn()
retryQueue.on("request-retry-failed", failListener)
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Mock failed response
fetchMock.mockRejectedValue(new Error("Network error"))
@@ -371,7 +371,7 @@ describe("RetryQueue", () => {
const maxRetriesListener = vi.fn()
retryQueue.on("request-max-retries-exceeded", maxRetriesListener)
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Mock failed responses
fetchMock.mockRejectedValue(new Error("Network error"))
@@ -400,7 +400,7 @@ describe("RetryQueue", () => {
it("should not process if already processing", async () => {
// Add a request
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Mock a slow response
fetchMock.mockImplementation(() => new Promise((resolve) => setTimeout(() => resolve({ ok: true }), 100)))
@@ -439,7 +439,7 @@ describe("RetryQueue", () => {
method: "POST",
headers: { "Content-Type": "application/json" },
},
- "telemetry",
+ "event",
)
fetchMock.mockResolvedValue({ ok: true })
@@ -465,7 +465,7 @@ describe("RetryQueue", () => {
// Create queue with custom timeout (short timeout for testing)
retryQueue = new RetryQueue(mockContext, { requestTimeout: 100 })
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Mock fetch to reject with abort error
const abortError = new Error("The operation was aborted")
@@ -496,7 +496,7 @@ describe("RetryQueue", () => {
retryQueue.on("request-retry-failed", failListener)
retryQueue.on("request-retry-success", successListener)
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// First attempt: 500 error
fetchMock.mockResolvedValueOnce({ ok: false, status: 500, statusText: "Internal Server Error" })
@@ -529,9 +529,9 @@ describe("RetryQueue", () => {
it("should pause entire queue on 429 rate limiting with Retry-After header", async () => {
// Add multiple requests to test queue-wide pause
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test3", { method: "POST" }, "event")
// Mock 429 response with Retry-After header (in seconds) for the first request
const retryAfterResponse = {
@@ -567,8 +567,8 @@ describe("RetryQueue", () => {
it("should process all requests after rate limit period expires", async () => {
// Add multiple requests
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
// Mock 429 response with very short Retry-After (for testing)
const retryAfterResponse = {
@@ -608,7 +608,7 @@ describe("RetryQueue", () => {
const successListener = vi.fn()
retryQueue.on("request-retry-success", successListener)
- await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test", { method: "POST" }, "event")
// Mock 401 error
fetchMock.mockResolvedValueOnce({ ok: false, status: 401, statusText: "Unauthorized" })
@@ -621,7 +621,7 @@ describe("RetryQueue", () => {
expect(stats.totalQueued).toBe(0)
// Test 403 as well
- await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test2", { method: "POST" }, "event")
fetchMock.mockResolvedValueOnce({ ok: false, status: 403, statusText: "Forbidden" })
await retryQueue.retryAll()
@@ -644,11 +644,7 @@ describe("RetryQueue", () => {
]
for (const error of clientErrors) {
- await retryQueue.enqueue(
- `https://api.example.com/test-${error.status}`,
- { method: "POST" },
- "telemetry",
- )
+ await retryQueue.enqueue(`https://api.example.com/test-${error.status}`, { method: "POST" }, "event")
fetchMock.mockResolvedValueOnce({ ok: false, ...error })
}
@@ -662,7 +658,7 @@ describe("RetryQueue", () => {
it("should prevent concurrent processing", async () => {
// Add a single request
- await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "telemetry")
+ await retryQueue.enqueue("https://api.example.com/test1", { method: "POST" }, "event")
// Mock slow response
let resolveFirst: () => void
diff --git a/packages/cloud/src/retry-queue/types.ts b/packages/cloud/src/retry-queue/types.ts
index 351ab71f08b..c6c5af06cff 100644
--- a/packages/cloud/src/retry-queue/types.ts
+++ b/packages/cloud/src/retry-queue/types.ts
@@ -4,7 +4,7 @@ export interface QueuedRequest {
options: RequestInit
timestamp: number
retryCount: number
- type: "api-call" | "telemetry" | "settings" | "other"
+ type: "api-call" | "event" | "settings" | "other"
operation?: string
lastError?: string
}
diff --git a/packages/evals/Dockerfile.runner b/packages/evals/Dockerfile.runner
index 5d8e1132061..8cd37fb67d8 100644
--- a/packages/evals/Dockerfile.runner
+++ b/packages/evals/Dockerfile.runner
@@ -115,7 +115,6 @@ RUN mkdir -p \
packages/core \
packages/evals \
packages/ipc \
- packages/telemetry \
packages/types \
packages/cloud \
packages/vscode-shim \
@@ -133,7 +132,6 @@ COPY ./packages/config-typescript/package.json ./packages/config-typescript/
COPY ./packages/core/package.json ./packages/core/
COPY ./packages/evals/package.json ./packages/evals/
COPY ./packages/ipc/package.json ./packages/ipc/
-COPY ./packages/telemetry/package.json ./packages/telemetry/
COPY ./packages/types/package.json ./packages/types/
COPY ./packages/cloud/package.json ./packages/cloud/
COPY ./packages/vscode-shim/package.json ./packages/vscode-shim/
diff --git a/packages/telemetry/CHANGELOG.md b/packages/telemetry/CHANGELOG.md
deleted file mode 100644
index 251acfd8228..00000000000
--- a/packages/telemetry/CHANGELOG.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# @roo-code/telemetry
-
-## 0.0.1
diff --git a/packages/telemetry/eslint.config.mjs b/packages/telemetry/eslint.config.mjs
deleted file mode 100644
index 694bf736642..00000000000
--- a/packages/telemetry/eslint.config.mjs
+++ /dev/null
@@ -1,4 +0,0 @@
-import { config } from "@roo-code/config-eslint/base"
-
-/** @type {import("eslint").Linter.Config} */
-export default [...config]
diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json
deleted file mode 100644
index dd31289e166..00000000000
--- a/packages/telemetry/package.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "name": "@roo-code/telemetry",
- "description": "Roo Code telemetry service and clients.",
- "version": "0.0.1",
- "type": "module",
- "exports": "./src/index.ts",
- "scripts": {
- "lint": "eslint src --ext=ts --max-warnings=0",
- "check-types": "tsc --noEmit",
- "test": "vitest run",
- "clean": "rimraf .turbo"
- },
- "dependencies": {
- "@roo-code/types": "workspace:^",
- "posthog-node": "^5.0.0",
- "zod": "^3.25.61"
- },
- "devDependencies": {
- "@roo-code/config-eslint": "workspace:^",
- "@roo-code/config-typescript": "workspace:^",
- "@types/node": "20.x",
- "@types/vscode": "^1.84.0",
- "vitest": "^3.2.3"
- }
-}
diff --git a/packages/telemetry/src/BaseTelemetryClient.ts b/packages/telemetry/src/BaseTelemetryClient.ts
deleted file mode 100644
index ea9c1917c47..00000000000
--- a/packages/telemetry/src/BaseTelemetryClient.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import {
- TelemetryEvent,
- TelemetryEventName,
- TelemetryClient,
- TelemetryPropertiesProvider,
- TelemetryEventSubscription,
-} from "@roo-code/types"
-
-export abstract class BaseTelemetryClient implements TelemetryClient {
- protected providerRef: WeakRef | null = null
- protected telemetryEnabled: boolean = false
-
- constructor(
- public readonly subscription?: TelemetryEventSubscription,
- protected readonly debug = false,
- ) {}
-
- protected isEventCapturable(eventName: TelemetryEventName): boolean {
- if (!this.subscription) {
- return true
- }
-
- return this.subscription.type === "include"
- ? this.subscription.events.includes(eventName)
- : !this.subscription.events.includes(eventName)
- }
-
- /**
- * Determines if a specific property should be included in telemetry events
- * Override in subclasses to filter specific properties
- */
- protected isPropertyCapturable(_propertyName: string): boolean {
- return true
- }
-
- protected async getEventProperties(event: TelemetryEvent): Promise {
- let providerProperties: TelemetryEvent["properties"] = {}
- const provider = this.providerRef?.deref()
-
- if (provider) {
- try {
- // Get properties from the provider
- providerProperties = await provider.getTelemetryProperties()
- } catch (error) {
- // Log error but continue with capturing the event.
- console.error(
- `Error getting telemetry properties: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- }
-
- // Merge provider properties with event-specific properties.
- // Event properties take precedence in case of conflicts.
- const mergedProperties = { ...providerProperties, ...(event.properties || {}) }
-
- // Filter out properties that shouldn't be captured by this client
- return Object.fromEntries(Object.entries(mergedProperties).filter(([key]) => this.isPropertyCapturable(key)))
- }
-
- public abstract capture(event: TelemetryEvent): Promise
-
- public abstract captureException(error: Error, additionalProperties?: Record): Promise
-
- public setProvider(provider: TelemetryPropertiesProvider): void {
- this.providerRef = new WeakRef(provider)
- }
-
- public abstract updateTelemetryState(didUserOptIn: boolean): void
-
- public isTelemetryEnabled(): boolean {
- return this.telemetryEnabled
- }
-
- public abstract shutdown(): Promise
-}
diff --git a/packages/telemetry/src/PostHogTelemetryClient.ts b/packages/telemetry/src/PostHogTelemetryClient.ts
deleted file mode 100644
index adc8b89a7a1..00000000000
--- a/packages/telemetry/src/PostHogTelemetryClient.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { PostHog } from "posthog-node"
-import * as vscode from "vscode"
-
-import {
- type TelemetryProperties,
- type TelemetryEvent,
- TelemetryEventName,
- getErrorStatusCode,
- getErrorMessage,
- shouldReportApiErrorToTelemetry,
- isApiProviderError,
- extractApiProviderErrorProperties,
- isConsecutiveMistakeError,
- extractConsecutiveMistakeErrorProperties,
-} from "@roo-code/types"
-
-import { BaseTelemetryClient } from "./BaseTelemetryClient"
-
-/**
- * PostHogTelemetryClient 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.
- */
-export class PostHogTelemetryClient extends BaseTelemetryClient {
- private client: PostHog
- private distinctId: string = vscode.env.machineId
- // Git repository properties that should be filtered out
- private readonly gitPropertyNames = ["repositoryUrl", "repositoryName", "defaultBranch"]
-
- constructor(debug = false) {
- super(
- {
- type: "exclude",
- events: [TelemetryEventName.TASK_MESSAGE, TelemetryEventName.LLM_COMPLETION],
- },
- debug,
- )
-
- this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { host: "https://ph.roocode.com" })
- }
-
- /**
- * Filter out git repository properties for PostHog telemetry
- * @param propertyName The property name to check
- * @returns Whether the property should be included in telemetry events
- */
- protected override isPropertyCapturable(propertyName: string): boolean {
- // Filter out git repository properties
- if (this.gitPropertyNames.includes(propertyName)) {
- return false
- }
- return true
- }
-
- public override async capture(event: TelemetryEvent): Promise {
- if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
- if (this.debug) {
- console.info(`[PostHogTelemetryClient#capture] Skipping event: ${event.event}`)
- }
-
- return
- }
-
- if (this.debug) {
- console.info(`[PostHogTelemetryClient#capture] ${event.event}`)
- }
-
- const properties = await this.getEventProperties(event)
-
- this.client.capture({
- distinctId: this.distinctId,
- event: event.event,
- properties,
- })
- }
-
- public override async captureException(
- error: Error,
- additionalProperties?: Record,
- ): Promise {
- if (!this.isTelemetryEnabled()) {
- if (this.debug) {
- console.info(`[PostHogTelemetryClient#captureException] Skipping exception: ${error.message}`)
- }
-
- return
- }
-
- // Extract error status code and message for filtering.
- const errorCode = getErrorStatusCode(error)
- const errorMessage = getErrorMessage(error) ?? error.message
-
- // Filter out expected errors (e.g., 402 billing, 429 rate limits)
- if (!shouldReportApiErrorToTelemetry(errorCode, errorMessage)) {
- if (this.debug) {
- console.info(
- `[PostHogTelemetryClient#captureException] Filtering out expected error: ${errorCode} - ${errorMessage}`,
- )
- }
- return
- }
-
- if (this.debug) {
- console.info(`[PostHogTelemetryClient#captureException] ${error.message}`)
- }
-
- // Auto-extract properties from known error types and merge with additionalProperties.
- // Explicit additionalProperties take precedence over auto-extracted properties.
- let mergedProperties = additionalProperties
-
- if (isApiProviderError(error)) {
- const extractedProperties = extractApiProviderErrorProperties(error)
- mergedProperties = { ...extractedProperties, ...additionalProperties }
- } else if (isConsecutiveMistakeError(error)) {
- const extractedProperties = extractConsecutiveMistakeErrorProperties(error)
- mergedProperties = { ...extractedProperties, ...additionalProperties }
- }
-
- // Override the error message with the extracted error message.
- error.message = errorMessage
-
- const provider = this.providerRef?.deref()
- let telemetryProperties: TelemetryProperties | undefined = undefined
-
- if (provider) {
- try {
- telemetryProperties = await provider.getTelemetryProperties()
- } catch (_error) {
- // Ignore.
- }
- }
-
- const exceptionProperties = {
- ...mergedProperties,
- $app_version: telemetryProperties?.appVersion,
- }
-
- this.client.captureException(error, this.distinctId, exceptionProperties)
- }
-
- /**
- * 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 override 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("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()
- }
- }
-
- public override async shutdown(): Promise {
- await this.client.shutdown()
- }
-}
diff --git a/packages/telemetry/src/TelemetryService.ts b/packages/telemetry/src/TelemetryService.ts
deleted file mode 100644
index 8eb1ed0ab67..00000000000
--- a/packages/telemetry/src/TelemetryService.ts
+++ /dev/null
@@ -1,291 +0,0 @@
-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): 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): 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): void {
- this.captureEvent(TelemetryEventName.CONTEXT_CONDENSED, {
- taskId,
- isAutomaticTrigger,
- ...(usedCustomPrompt !== undefined && { usedCustomPrompt }),
- })
- }
-
- 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,
- ): 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 {
- 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
- }
-}
diff --git a/packages/telemetry/src/__tests__/PostHogTelemetryClient.test.ts b/packages/telemetry/src/__tests__/PostHogTelemetryClient.test.ts
deleted file mode 100644
index 91175952f6c..00000000000
--- a/packages/telemetry/src/__tests__/PostHogTelemetryClient.test.ts
+++ /dev/null
@@ -1,629 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-// pnpm --filter @roo-code/telemetry test src/__tests__/PostHogTelemetryClient.test.ts
-
-import * as vscode from "vscode"
-import { PostHog } from "posthog-node"
-
-import { type TelemetryPropertiesProvider, TelemetryEventName, ApiProviderError } from "@roo-code/types"
-
-import { PostHogTelemetryClient } from "../PostHogTelemetryClient"
-
-vi.mock("posthog-node")
-
-vi.mock("vscode", () => ({
- env: {
- machineId: "test-machine-id",
- },
- workspace: {
- getConfiguration: vi.fn(),
- },
-}))
-
-describe("PostHogTelemetryClient", () => {
- const getPrivateProperty = (instance: any, propertyName: string): T => {
- return instance[propertyName]
- }
-
- let mockPostHogClient: any
-
- beforeEach(() => {
- vi.clearAllMocks()
-
- mockPostHogClient = {
- capture: vi.fn(),
- captureException: vi.fn(),
- optIn: vi.fn(),
- optOut: vi.fn(),
- shutdown: vi.fn().mockResolvedValue(undefined),
- }
- ;(PostHog as any).mockImplementation(() => mockPostHogClient)
-
- // @ts-expect-error - Accessing private static property for testing
- PostHogTelemetryClient._instance = undefined
- ;(vscode.workspace.getConfiguration as any).mockReturnValue({
- get: vi.fn().mockReturnValue("all"),
- })
- })
-
- describe("isEventCapturable", () => {
- it("should return true for events not in exclude list", () => {
- const client = new PostHogTelemetryClient()
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.TASK_CREATED)).toBe(true)
- expect(isEventCapturable(TelemetryEventName.MODE_SWITCH)).toBe(true)
- })
-
- it("should return false for events in exclude list", () => {
- const client = new PostHogTelemetryClient()
-
- const isEventCapturable = getPrivateProperty<(eventName: TelemetryEventName) => boolean>(
- client,
- "isEventCapturable",
- ).bind(client)
-
- expect(isEventCapturable(TelemetryEventName.LLM_COMPLETION)).toBe(false)
- })
- })
-
- describe("isPropertyCapturable", () => {
- it("should filter out git repository properties", () => {
- const client = new PostHogTelemetryClient()
-
- const isPropertyCapturable = getPrivateProperty<(propertyName: string) => boolean>(
- client,
- "isPropertyCapturable",
- ).bind(client)
-
- // Git properties should be filtered out
- expect(isPropertyCapturable("repositoryUrl")).toBe(false)
- expect(isPropertyCapturable("repositoryName")).toBe(false)
- expect(isPropertyCapturable("defaultBranch")).toBe(false)
-
- // Other properties should be included
- expect(isPropertyCapturable("appVersion")).toBe(true)
- expect(isPropertyCapturable("vscodeVersion")).toBe(true)
- expect(isPropertyCapturable("platform")).toBe(true)
- expect(isPropertyCapturable("mode")).toBe(true)
- expect(isPropertyCapturable("customProperty")).toBe(true)
- })
- })
-
- describe("getEventProperties", () => {
- it("should merge provider properties with event properties", async () => {
- const client = new PostHogTelemetryClient()
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }),
- }
-
- client.setProvider(mockProvider)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: {
- customProp: "value",
- mode: "override", // This should override the provider's mode.
- },
- })
-
- expect(result).toEqual({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "override", // Event property takes precedence.
- customProp: "value",
- })
-
- expect(mockProvider.getTelemetryProperties).toHaveBeenCalledTimes(1)
- })
-
- it("should filter out git repository properties", async () => {
- const client = new PostHogTelemetryClient()
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- // Git properties that should be filtered out
- repositoryUrl: "https://github.com/example/repo",
- repositoryName: "example/repo",
- defaultBranch: "main",
- }),
- }
-
- client.setProvider(mockProvider)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: {
- customProp: "value",
- },
- })
-
- // Git properties should be filtered out
- expect(result).not.toHaveProperty("repositoryUrl")
- expect(result).not.toHaveProperty("repositoryName")
- expect(result).not.toHaveProperty("defaultBranch")
-
- // Other properties should be included
- expect(result).toEqual({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- customProp: "value",
- })
- })
-
- it("should handle errors from provider gracefully", async () => {
- const client = new PostHogTelemetryClient()
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
- }
-
- const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
- client.setProvider(mockProvider)
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: { customProp: "value" },
- })
-
- expect(result).toEqual({ customProp: "value" })
- expect(consoleErrorSpy).toHaveBeenCalledWith(
- expect.stringContaining("Error getting telemetry properties: Provider error"),
- )
-
- consoleErrorSpy.mockRestore()
- })
-
- it("should return event properties when no provider is set", async () => {
- const client = new PostHogTelemetryClient()
-
- const getEventProperties = getPrivateProperty<
- (event: { event: TelemetryEventName; properties?: Record }) => Promise>
- >(client, "getEventProperties").bind(client)
-
- const result = await getEventProperties({
- event: TelemetryEventName.TASK_CREATED,
- properties: { customProp: "value" },
- })
-
- expect(result).toEqual({ customProp: "value" })
- })
- })
-
- describe("capture", () => {
- it("should not capture events when telemetry is disabled", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(false)
-
- await client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: { test: "value" },
- })
-
- expect(mockPostHogClient.capture).not.toHaveBeenCalled()
- })
-
- it("should not capture events that are not capturable", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- await client.capture({
- event: TelemetryEventName.LLM_COMPLETION, // This is in the exclude list.
- properties: { test: "value" },
- })
-
- expect(mockPostHogClient.capture).not.toHaveBeenCalled()
- })
-
- it("should capture events when telemetry is enabled and event is capturable", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }),
- }
-
- client.setProvider(mockProvider)
-
- await client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: { test: "value" },
- })
-
- expect(mockPostHogClient.capture).toHaveBeenCalledWith({
- distinctId: "test-machine-id",
- event: TelemetryEventName.TASK_CREATED,
- properties: expect.objectContaining({
- appVersion: "1.0.0",
- test: "value",
- }),
- })
- })
-
- it("should filter out git repository properties when capturing events", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- // Git properties that should be filtered out
- repositoryUrl: "https://github.com/example/repo",
- repositoryName: "example/repo",
- defaultBranch: "main",
- }),
- }
-
- client.setProvider(mockProvider)
-
- await client.capture({
- event: TelemetryEventName.TASK_CREATED,
- properties: { test: "value" },
- })
-
- expect(mockPostHogClient.capture).toHaveBeenCalledWith({
- distinctId: "test-machine-id",
- event: TelemetryEventName.TASK_CREATED,
- properties: expect.objectContaining({
- appVersion: "1.0.0",
- test: "value",
- }),
- })
-
- // Verify git properties are not included
- const captureCall = mockPostHogClient.capture.mock.calls[0][0]
- expect(captureCall.properties).not.toHaveProperty("repositoryUrl")
- expect(captureCall.properties).not.toHaveProperty("repositoryName")
- expect(captureCall.properties).not.toHaveProperty("defaultBranch")
- })
- })
-
- describe("updateTelemetryState", () => {
- it("should enable telemetry when user opts in and global telemetry is enabled", () => {
- const client = new PostHogTelemetryClient()
-
- ;(vscode.workspace.getConfiguration as any).mockReturnValue({
- get: vi.fn().mockReturnValue("all"),
- })
-
- client.updateTelemetryState(true)
-
- expect(client.isTelemetryEnabled()).toBe(true)
- expect(mockPostHogClient.optIn).toHaveBeenCalled()
- })
-
- it("should disable telemetry when user opts out", () => {
- const client = new PostHogTelemetryClient()
-
- ;(vscode.workspace.getConfiguration as any).mockReturnValue({
- get: vi.fn().mockReturnValue("all"),
- })
-
- client.updateTelemetryState(false)
-
- expect(client.isTelemetryEnabled()).toBe(false)
- expect(mockPostHogClient.optOut).toHaveBeenCalled()
- })
-
- it("should disable telemetry when global telemetry is disabled, regardless of user opt-in", () => {
- const client = new PostHogTelemetryClient()
-
- ;(vscode.workspace.getConfiguration as any).mockReturnValue({
- get: vi.fn().mockReturnValue("off"),
- })
-
- client.updateTelemetryState(true)
- expect(client.isTelemetryEnabled()).toBe(false)
- expect(mockPostHogClient.optOut).toHaveBeenCalled()
- })
- })
-
- describe("captureException", () => {
- it("should not capture exceptions when telemetry is disabled", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(false)
-
- const error = new Error("Test error")
- await client.captureException(error)
-
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should capture exceptions with app version from provider", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const mockProvider: TelemetryPropertiesProvider = {
- getTelemetryProperties: vi.fn().mockResolvedValue({
- appVersion: "1.0.0",
- vscodeVersion: "1.60.0",
- platform: "darwin",
- editorName: "vscode",
- language: "en",
- mode: "code",
- }),
- }
-
- client.setProvider(mockProvider)
-
- const error = new Error("Test error")
- await client.captureException(error, { customProp: "value" })
-
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(
- error,
- "test-machine-id",
- expect.objectContaining({
- customProp: "value",
- $app_version: "1.0.0",
- }),
- )
- })
-
- it("should capture exceptions with undefined app version when no provider is set", async () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new Error("Test error")
- await client.captureException(error)
-
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
- })
-
- describe("shutdown", () => {
- it("should call shutdown on the PostHog client", async () => {
- const client = new PostHogTelemetryClient()
- await client.shutdown()
- expect(mockPostHogClient.shutdown).toHaveBeenCalled()
- })
- })
-
- describe("captureException error filtering", () => {
- it("should filter out 429 rate limit errors (via status property)", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an error with status property (like OpenAI SDK errors)
- const error = Object.assign(new Error("Rate limit exceeded"), { status: 429 })
- client.captureException(error)
-
- // Should NOT capture 429 errors
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should filter out 402 billing errors (via status property)", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an error with status 402 (Payment Required)
- const error = Object.assign(new Error("Payment required"), { status: 402 })
- client.captureException(error)
-
- // Should NOT capture 402 errors
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should filter out errors with '429' in message", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new Error("429 Rate limit exceeded: free-models-per-day")
- client.captureException(error)
-
- // Should NOT capture errors with 429 in message
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should filter out errors containing 'rate limit' (case insensitive)", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new Error("Request failed due to Rate Limit")
- client.captureException(error)
-
- // Should NOT capture rate limit errors
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should capture non-rate-limit errors", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new Error("Internal server error")
- client.captureException(error)
-
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
-
- it("should capture errors with non-429 status codes", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = Object.assign(new Error("Internal server error"), { status: 500 })
- client.captureException(error)
-
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
-
- it("should use nested error message from OpenAI SDK error structure for filtering", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an error with nested metadata (like OpenRouter upstream errors)
- const error = Object.assign(new Error("Request failed"), {
- status: 429,
- error: {
- message: "Error details",
- metadata: { raw: "Rate limit exceeded: free-models-per-day" },
- },
- })
- client.captureException(error)
-
- // Should NOT capture - the nested metadata.raw contains rate limit message
- expect(mockPostHogClient.captureException).not.toHaveBeenCalled()
- })
-
- it("should capture errors with nested metadata and override error.message with extracted message", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an OpenAI SDK-like error with nested metadata (non-rate-limit error)
- const error = Object.assign(new Error("Generic request failed"), {
- status: 500,
- error: {
- message: "Nested error message",
- metadata: { raw: "Upstream provider error: model overloaded" },
- },
- })
-
- client.captureException(error)
-
- // The implementation overrides error.message with the extracted message from getErrorMessage
- expect(error.message).toBe("Upstream provider error: model overloaded")
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
-
- it("should capture errors with nested error.message and override error.message with extracted message", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an OpenAI SDK-like error with nested message but no metadata.raw
- const error = Object.assign(new Error("Generic request failed"), {
- status: 500,
- error: {
- message: "Upstream provider: connection timeout",
- },
- })
-
- client.captureException(error)
-
- // The implementation overrides error.message with the extracted message from getErrorMessage
- expect(error.message).toBe("Upstream provider: connection timeout")
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
-
- it("should use primary message when no nested error structure exists", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- // Create an OpenAI SDK-like error without nested error object
- const error = Object.assign(new Error("Primary error message"), {
- status: 500,
- })
-
- client.captureException(error)
-
- // Verify error message remains the primary message
- expect(error.message).toBe("Primary error message")
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- $app_version: undefined,
- })
- })
-
- it("should capture ApiProviderError and auto-extract properties", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage", 500)
- client.captureException(error)
-
- // The implementation auto-extracts properties from ApiProviderError
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- provider: "OpenRouter",
- modelId: "gpt-4",
- operation: "createMessage",
- errorCode: 500,
- $app_version: undefined,
- })
- })
-
- it("should capture ApiProviderError with additionalProperties merged with auto-extracted properties", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- client.captureException(error, { customProperty: "value" })
-
- // additionalProperties take precedence over auto-extracted properties
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- provider: "OpenRouter",
- modelId: "gpt-4",
- operation: "createMessage",
- customProperty: "value",
- $app_version: undefined,
- })
- })
-
- it("should capture regular errors with additionalProperties", () => {
- const client = new PostHogTelemetryClient()
- client.updateTelemetryState(true)
-
- const error = new Error("Regular error")
- client.captureException(error, { customProperty: "value" })
-
- expect(mockPostHogClient.captureException).toHaveBeenCalledWith(error, "test-machine-id", {
- customProperty: "value",
- $app_version: undefined,
- })
- })
- })
-})
diff --git a/packages/telemetry/src/index.ts b/packages/telemetry/src/index.ts
deleted file mode 100644
index 8795ad46a2e..00000000000
--- a/packages/telemetry/src/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from "./BaseTelemetryClient"
-export * from "./PostHogTelemetryClient"
-export * from "./TelemetryService"
diff --git a/packages/telemetry/tsconfig.json b/packages/telemetry/tsconfig.json
deleted file mode 100644
index f599e2220dd..00000000000
--- a/packages/telemetry/tsconfig.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "extends": "@roo-code/config-typescript/vscode-library.json",
- "include": ["src"],
- "exclude": ["node_modules"]
-}
diff --git a/packages/telemetry/vitest.config.ts b/packages/telemetry/vitest.config.ts
deleted file mode 100644
index b6d6dbb880f..00000000000
--- a/packages/telemetry/vitest.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from "vitest/config"
-
-export default defineConfig({
- test: {
- globals: true,
- environment: "node",
- watch: false,
- },
-})
diff --git a/packages/types/src/__tests__/telemetry.test.ts b/packages/types/src/__tests__/telemetry.test.ts
deleted file mode 100644
index 29e6207794b..00000000000
--- a/packages/types/src/__tests__/telemetry.test.ts
+++ /dev/null
@@ -1,576 +0,0 @@
-// pnpm --filter @roo-code/types test src/__tests__/telemetry.test.ts
-
-import {
- getErrorStatusCode,
- getErrorMessage,
- extractMessageFromJsonPayload,
- shouldReportApiErrorToTelemetry,
- EXPECTED_API_ERROR_CODES,
- ApiProviderError,
- isApiProviderError,
- extractApiProviderErrorProperties,
- ConsecutiveMistakeError,
- isConsecutiveMistakeError,
- extractConsecutiveMistakeErrorProperties,
-} from "../telemetry.js"
-
-describe("telemetry error utilities", () => {
- describe("getErrorStatusCode", () => {
- it("should return undefined for non-object errors", () => {
- expect(getErrorStatusCode(null)).toBeUndefined()
- expect(getErrorStatusCode(undefined)).toBeUndefined()
- expect(getErrorStatusCode("error string")).toBeUndefined()
- expect(getErrorStatusCode(42)).toBeUndefined()
- })
-
- it("should return undefined for objects without status property", () => {
- expect(getErrorStatusCode({})).toBeUndefined()
- expect(getErrorStatusCode({ message: "error" })).toBeUndefined()
- expect(getErrorStatusCode({ code: 500 })).toBeUndefined()
- })
-
- it("should return undefined for objects with non-numeric status", () => {
- expect(getErrorStatusCode({ status: "500" })).toBeUndefined()
- expect(getErrorStatusCode({ status: null })).toBeUndefined()
- expect(getErrorStatusCode({ status: undefined })).toBeUndefined()
- })
-
- it("should return status for OpenAI SDK-like errors", () => {
- const error = { status: 429, message: "Rate limit exceeded" }
- expect(getErrorStatusCode(error)).toBe(429)
- })
-
- it("should return status for errors with additional properties", () => {
- const error = {
- status: 500,
- code: "internal_error",
- message: "Internal server error",
- error: { message: "Upstream error" },
- }
- expect(getErrorStatusCode(error)).toBe(500)
- })
- })
-
- describe("getErrorMessage", () => {
- it("should return undefined for null, undefined, or objects without message", () => {
- expect(getErrorMessage(null)).toBeUndefined()
- expect(getErrorMessage(undefined)).toBeUndefined()
- expect(getErrorMessage({})).toBeUndefined()
- expect(getErrorMessage({ code: 500 })).toBeUndefined()
- })
-
- it("should return the primary message for simple OpenAI SDK errors", () => {
- const error = { status: 400, message: "Bad request" }
- expect(getErrorMessage(error)).toBe("Bad request")
- })
-
- it("should return message from plain objects with message property", () => {
- expect(getErrorMessage({ message: "error" })).toBe("error")
- })
-
- it("should prioritize nested error.message over primary message", () => {
- const error = {
- status: 500,
- message: "Request failed",
- error: { message: "Upstream provider error" },
- }
- expect(getErrorMessage(error)).toBe("Upstream provider error")
- })
-
- it("should prioritize metadata.raw over other messages", () => {
- const error = {
- status: 429,
- message: "Request failed",
- error: {
- message: "Error details",
- metadata: { raw: "Rate limit exceeded: free-models-per-day" },
- },
- }
- expect(getErrorMessage(error)).toBe("Rate limit exceeded: free-models-per-day")
- })
-
- it("should fallback to nested error.message when metadata.raw is undefined", () => {
- const error = {
- status: 400,
- message: "Request failed",
- error: {
- message: "Detailed error message",
- metadata: {},
- },
- }
- expect(getErrorMessage(error)).toBe("Detailed error message")
- })
-
- it("should fallback to primary message when no nested messages exist", () => {
- const error = {
- status: 403,
- message: "Forbidden",
- error: {},
- }
- expect(getErrorMessage(error)).toBe("Forbidden")
- })
-
- it("should extract message from JSON payload in error message", () => {
- const error = {
- status: 503,
- message: '503 {"error":{"code":"","message":"Model unavailable"}}',
- }
- expect(getErrorMessage(error)).toBe("Model unavailable")
- })
-
- it("should extract message from JSON payload with status prefix", () => {
- const error = {
- status: 503,
- message:
- '503 {"error":{"code":"","message":"所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道,请更换分组尝试"}}',
- }
- expect(getErrorMessage(error)).toBe(
- "所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道,请更换分组尝试",
- )
- })
-
- it("should extract message from nested error.message containing JSON", () => {
- const error = {
- status: 500,
- message: "Request failed",
- error: { message: '{"error":{"message":"Upstream provider error"}}' },
- }
- expect(getErrorMessage(error)).toBe("Upstream provider error")
- })
-
- it("should return original message when JSON has no message field", () => {
- const error = {
- status: 500,
- message: '{"error":{"code":"123"}}',
- }
- expect(getErrorMessage(error)).toBe('{"error":{"code":"123"}}')
- })
-
- it("should return original message when JSON is invalid", () => {
- const error = {
- status: 500,
- message: "503 {invalid json}",
- }
- expect(getErrorMessage(error)).toBe("503 {invalid json}")
- })
-
- it("should extract message from standard Error object", () => {
- const error = new Error("Simple error message")
- expect(getErrorMessage(error)).toBe("Simple error message")
- })
-
- it("should extract message from standard Error with JSON payload", () => {
- const error = new Error('503 {"error":{"code":"","message":"Model unavailable"}}')
- expect(getErrorMessage(error)).toBe("Model unavailable")
- })
-
- it("should extract message from ApiProviderError", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- expect(getErrorMessage(error)).toBe("Test error")
- })
-
- it("should extract message from ApiProviderError with JSON payload", () => {
- const jsonMessage =
- '503 {"error":{"code":"","message":"所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道"}}'
- const error = new ApiProviderError(jsonMessage, "Anthropic", "claude-sonnet-4-5", "createMessage")
- expect(getErrorMessage(error)).toBe("所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道")
- })
-
- it("should handle ApiProviderError with errorCode but no status property", () => {
- const error = new ApiProviderError("Test error", "Anthropic", "claude-3-opus", "createMessage", 500)
- expect(getErrorMessage(error)).toBe("Test error")
- })
- })
-
- describe("extractMessageFromJsonPayload", () => {
- it("should return undefined for messages without JSON", () => {
- expect(extractMessageFromJsonPayload("Simple error message")).toBeUndefined()
- expect(extractMessageFromJsonPayload("Error: something went wrong")).toBeUndefined()
- expect(extractMessageFromJsonPayload("")).toBeUndefined()
- })
-
- it("should extract message from error.message structure", () => {
- const json = '{"error":{"message":"Model unavailable"}}'
- expect(extractMessageFromJsonPayload(json)).toBe("Model unavailable")
- })
-
- it("should extract message from error.message with code structure", () => {
- const json = '{"error":{"code":"","message":"Model unavailable"}}'
- expect(extractMessageFromJsonPayload(json)).toBe("Model unavailable")
- })
-
- it("should extract message from status prefix followed by JSON", () => {
- const message = '503 {"error":{"code":"","message":"Model unavailable"}}'
- expect(extractMessageFromJsonPayload(message)).toBe("Model unavailable")
- })
-
- it("should extract message from simple message structure", () => {
- const json = '{"message":"Simple error"}'
- expect(extractMessageFromJsonPayload(json)).toBe("Simple error")
- })
-
- it("should return undefined for JSON without message field", () => {
- const json = '{"error":{"code":"500"}}'
- expect(extractMessageFromJsonPayload(json)).toBeUndefined()
- })
-
- it("should return undefined for invalid JSON", () => {
- expect(extractMessageFromJsonPayload("{invalid json}")).toBeUndefined()
- expect(extractMessageFromJsonPayload("503 {not: valid: json}")).toBeUndefined()
- })
-
- it("should handle nested error structure with empty code", () => {
- const json = '{"error":{"code":"","message":"Token quota exceeded"}}'
- expect(extractMessageFromJsonPayload(json)).toBe("Token quota exceeded")
- })
-
- it("should handle Unicode messages correctly", () => {
- const json = '{"error":{"message":"所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道"}}'
- expect(extractMessageFromJsonPayload(json)).toBe(
- "所有令牌分组 Tier 3 下对于模型 claude-sonnet-4-5 均无可用渠道",
- )
- })
-
- it("should return undefined when message field is not a string", () => {
- const json = '{"error":{"message":123}}'
- expect(extractMessageFromJsonPayload(json)).toBeUndefined()
- })
- })
-
- describe("shouldReportApiErrorToTelemetry", () => {
- it("should return false for expected error codes", () => {
- for (const code of EXPECTED_API_ERROR_CODES) {
- expect(shouldReportApiErrorToTelemetry(code)).toBe(false)
- }
- })
-
- it("should return false for 402 billing errors", () => {
- expect(shouldReportApiErrorToTelemetry(402)).toBe(false)
- expect(shouldReportApiErrorToTelemetry(402, "Payment required")).toBe(false)
- })
-
- it("should return false for 429 rate limit errors", () => {
- expect(shouldReportApiErrorToTelemetry(429)).toBe(false)
- expect(shouldReportApiErrorToTelemetry(429, "Rate limit exceeded")).toBe(false)
- })
-
- it("should return false for messages starting with 429", () => {
- expect(shouldReportApiErrorToTelemetry(undefined, "429 Rate limit exceeded")).toBe(false)
- expect(shouldReportApiErrorToTelemetry(undefined, "429: Too many requests")).toBe(false)
- })
-
- it("should return false for messages containing 'rate limit' (case insensitive)", () => {
- expect(shouldReportApiErrorToTelemetry(undefined, "Rate limit exceeded")).toBe(false)
- expect(shouldReportApiErrorToTelemetry(undefined, "RATE LIMIT error")).toBe(false)
- expect(shouldReportApiErrorToTelemetry(undefined, "Request failed due to rate limit")).toBe(false)
- })
-
- it("should return true for non-rate-limit errors", () => {
- expect(shouldReportApiErrorToTelemetry(500)).toBe(true)
- expect(shouldReportApiErrorToTelemetry(400, "Bad request")).toBe(true)
- expect(shouldReportApiErrorToTelemetry(401, "Unauthorized")).toBe(true)
- })
-
- it("should return true when no error code or message is provided", () => {
- expect(shouldReportApiErrorToTelemetry()).toBe(true)
- expect(shouldReportApiErrorToTelemetry(undefined, undefined)).toBe(true)
- })
-
- it("should return true for regular error messages without rate limit keywords", () => {
- expect(shouldReportApiErrorToTelemetry(undefined, "Internal server error")).toBe(true)
- expect(shouldReportApiErrorToTelemetry(undefined, "Connection timeout")).toBe(true)
- })
- })
-
- describe("EXPECTED_API_ERROR_CODES", () => {
- it("should contain 402 (payment required)", () => {
- expect(EXPECTED_API_ERROR_CODES.has(402)).toBe(true)
- })
-
- it("should contain 429 (rate limit)", () => {
- expect(EXPECTED_API_ERROR_CODES.has(429)).toBe(true)
- })
- })
-
- describe("ApiProviderError", () => {
- it("should create an error with correct properties", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage", 500)
-
- expect(error.message).toBe("Test error")
- expect(error.name).toBe("ApiProviderError")
- expect(error.provider).toBe("OpenRouter")
- expect(error.modelId).toBe("gpt-4")
- expect(error.operation).toBe("createMessage")
- expect(error.errorCode).toBe(500)
- })
-
- it("should work without optional errorCode", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
-
- expect(error.message).toBe("Test error")
- expect(error.provider).toBe("OpenRouter")
- expect(error.modelId).toBe("gpt-4")
- expect(error.operation).toBe("createMessage")
- expect(error.errorCode).toBeUndefined()
- })
-
- it("should be an instance of Error", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- expect(error).toBeInstanceOf(Error)
- })
- })
-
- describe("isApiProviderError", () => {
- it("should return true for ApiProviderError instances", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- expect(isApiProviderError(error)).toBe(true)
- })
-
- it("should return true for ApiProviderError with errorCode", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage", 429)
- expect(isApiProviderError(error)).toBe(true)
- })
-
- it("should return false for regular Error instances", () => {
- const error = new Error("Test error")
- expect(isApiProviderError(error)).toBe(false)
- })
-
- it("should return false for null and undefined", () => {
- expect(isApiProviderError(null)).toBe(false)
- expect(isApiProviderError(undefined)).toBe(false)
- })
-
- it("should return false for non-error objects", () => {
- expect(isApiProviderError({})).toBe(false)
- expect(isApiProviderError({ provider: "test", modelId: "test", operation: "test" })).toBe(false)
- })
-
- it("should return false for Error with wrong name", () => {
- const error = new Error("Test error")
- error.name = "CustomError"
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).provider = "OpenRouter"
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).modelId = "gpt-4"
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).operation = "createMessage"
- expect(isApiProviderError(error)).toBe(false)
- })
- })
-
- describe("extractApiProviderErrorProperties", () => {
- it("should extract all properties from ApiProviderError", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage", 500)
- const properties = extractApiProviderErrorProperties(error)
-
- expect(properties).toEqual({
- provider: "OpenRouter",
- modelId: "gpt-4",
- operation: "createMessage",
- errorCode: 500,
- })
- })
-
- it("should not include errorCode when undefined", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- const properties = extractApiProviderErrorProperties(error)
-
- expect(properties).toEqual({
- provider: "OpenRouter",
- modelId: "gpt-4",
- operation: "createMessage",
- })
- expect(properties).not.toHaveProperty("errorCode")
- })
-
- it("should include errorCode when it is 0", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage", 0)
- const properties = extractApiProviderErrorProperties(error)
-
- // errorCode of 0 is falsy but !== undefined, so it should be included
- expect(properties).toHaveProperty("errorCode", 0)
- })
- })
-
- describe("ConsecutiveMistakeError", () => {
- it("should create an error with correct properties", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 5, 3, "no_tools_used")
-
- expect(error.message).toBe("Test error")
- expect(error.name).toBe("ConsecutiveMistakeError")
- expect(error.taskId).toBe("task-123")
- expect(error.consecutiveMistakeCount).toBe(5)
- expect(error.consecutiveMistakeLimit).toBe(3)
- expect(error.reason).toBe("no_tools_used")
- })
-
- it("should create an error with provider and modelId", () => {
- const error = new ConsecutiveMistakeError(
- "Test error",
- "task-123",
- 5,
- 3,
- "no_tools_used",
- "anthropic",
- "claude-3-sonnet-20240229",
- )
-
- expect(error.message).toBe("Test error")
- expect(error.name).toBe("ConsecutiveMistakeError")
- expect(error.taskId).toBe("task-123")
- expect(error.consecutiveMistakeCount).toBe(5)
- expect(error.consecutiveMistakeLimit).toBe(3)
- expect(error.reason).toBe("no_tools_used")
- expect(error.provider).toBe("anthropic")
- expect(error.modelId).toBe("claude-3-sonnet-20240229")
- })
-
- it("should be an instance of Error", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3)
- expect(error).toBeInstanceOf(Error)
- })
-
- it("should handle zero values", () => {
- const error = new ConsecutiveMistakeError("Zero test", "task-000", 0, 0)
-
- expect(error.taskId).toBe("task-000")
- expect(error.consecutiveMistakeCount).toBe(0)
- expect(error.consecutiveMistakeLimit).toBe(0)
- })
-
- it("should default reason to unknown when not provided", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3)
- expect(error.reason).toBe("unknown")
- })
-
- it("should accept tool_repetition reason", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3, "tool_repetition")
- expect(error.reason).toBe("tool_repetition")
- })
-
- it("should accept no_tools_used reason", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3, "no_tools_used")
- expect(error.reason).toBe("no_tools_used")
- })
-
- it("should have undefined provider and modelId when not provided", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3, "no_tools_used")
- expect(error.provider).toBeUndefined()
- expect(error.modelId).toBeUndefined()
- })
- })
-
- describe("isConsecutiveMistakeError", () => {
- it("should return true for ConsecutiveMistakeError instances", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 3, 3)
- expect(isConsecutiveMistakeError(error)).toBe(true)
- })
-
- it("should return false for regular Error instances", () => {
- const error = new Error("Test error")
- expect(isConsecutiveMistakeError(error)).toBe(false)
- })
-
- it("should return false for ApiProviderError instances", () => {
- const error = new ApiProviderError("Test error", "OpenRouter", "gpt-4", "createMessage")
- expect(isConsecutiveMistakeError(error)).toBe(false)
- })
-
- it("should return false for null and undefined", () => {
- expect(isConsecutiveMistakeError(null)).toBe(false)
- expect(isConsecutiveMistakeError(undefined)).toBe(false)
- })
-
- it("should return false for non-error objects", () => {
- expect(isConsecutiveMistakeError({})).toBe(false)
- expect(
- isConsecutiveMistakeError({
- taskId: "task-123",
- consecutiveMistakeCount: 3,
- consecutiveMistakeLimit: 3,
- }),
- ).toBe(false)
- })
-
- it("should return false for Error with wrong name", () => {
- const error = new Error("Test error")
- error.name = "CustomError"
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).taskId = "task-123"
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).consecutiveMistakeCount = 3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(error as any).consecutiveMistakeLimit = 3
- expect(isConsecutiveMistakeError(error)).toBe(false)
- })
- })
-
- describe("extractConsecutiveMistakeErrorProperties", () => {
- it("should extract all properties from ConsecutiveMistakeError", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 5, 3, "no_tools_used")
- const properties = extractConsecutiveMistakeErrorProperties(error)
-
- expect(properties).toEqual({
- taskId: "task-123",
- consecutiveMistakeCount: 5,
- consecutiveMistakeLimit: 3,
- reason: "no_tools_used",
- })
- })
-
- it("should extract all properties including provider and modelId", () => {
- const error = new ConsecutiveMistakeError(
- "Test error",
- "task-123",
- 5,
- 3,
- "no_tools_used",
- "anthropic",
- "claude-3-sonnet-20240229",
- )
- const properties = extractConsecutiveMistakeErrorProperties(error)
-
- expect(properties).toEqual({
- taskId: "task-123",
- consecutiveMistakeCount: 5,
- consecutiveMistakeLimit: 3,
- reason: "no_tools_used",
- provider: "anthropic",
- modelId: "claude-3-sonnet-20240229",
- })
- })
-
- it("should not include provider and modelId when undefined", () => {
- const error = new ConsecutiveMistakeError("Test error", "task-123", 5, 3, "no_tools_used")
- const properties = extractConsecutiveMistakeErrorProperties(error)
-
- expect(properties).not.toHaveProperty("provider")
- expect(properties).not.toHaveProperty("modelId")
- })
-
- it("should handle zero values correctly", () => {
- const error = new ConsecutiveMistakeError("Zero test", "task-000", 0, 0)
- const properties = extractConsecutiveMistakeErrorProperties(error)
-
- expect(properties).toEqual({
- taskId: "task-000",
- consecutiveMistakeCount: 0,
- consecutiveMistakeLimit: 0,
- reason: "unknown",
- })
- })
-
- it("should handle large numbers", () => {
- const error = new ConsecutiveMistakeError("Large test", "task-large", 1000, 500, "tool_repetition")
- const properties = extractConsecutiveMistakeErrorProperties(error)
-
- expect(properties).toEqual({
- taskId: "task-large",
- consecutiveMistakeCount: 1000,
- consecutiveMistakeLimit: 500,
- reason: "tool_repetition",
- })
- })
- })
-})
diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts
index c991cdb1e6e..6a684241170 100644
--- a/packages/types/src/cloud.ts
+++ b/packages/types/src/cloud.ts
@@ -8,7 +8,21 @@ import { globalSettingsSchema } from "./global-settings.js"
import { providerSettingsWithIdSchema } from "./provider-settings.js"
import { mcpMarketplaceItemSchema } from "./marketplace.js"
import { clineMessageSchema, queuedMessageSchema, tokenUsageSchema } from "./message.js"
-import { staticAppPropertiesSchema, gitPropertiesSchema } from "./telemetry.js"
+
+const extensionAppPropertiesSchema = z.object({
+ appName: z.string(),
+ appVersion: z.string(),
+ vscodeVersion: z.string(),
+ platform: z.string(),
+ editorName: z.string(),
+ hostname: z.string().optional(),
+})
+
+const extensionGitPropertiesSchema = z.object({
+ repositoryUrl: z.string().optional(),
+ repositoryName: z.string().optional(),
+ defaultBranch: z.string().optional(),
+})
/**
* JWTPayload
@@ -409,8 +423,8 @@ export const extensionInstanceSchema = z.object({
instanceId: z.string(),
userId: z.string(),
workspacePath: z.string(),
- appProperties: staticAppPropertiesSchema,
- gitProperties: gitPropertiesSchema.optional(),
+ appProperties: extensionAppPropertiesSchema,
+ gitProperties: extensionGitPropertiesSchema.optional(),
lastHeartbeat: z.coerce.number(),
task: extensionTaskSchema,
taskAsk: clineMessageSchema.optional(),
diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts
index 288f6c2118c..a976be95da2 100644
--- a/packages/types/src/global-settings.ts
+++ b/packages/types/src/global-settings.ts
@@ -10,7 +10,6 @@ import {
import { historyItemSchema } from "./history.js"
import { codebaseIndexModelsSchema, codebaseIndexConfigSchema } from "./codebase-index.js"
import { experimentsSchema } from "./experiment.js"
-import { telemetrySettingsSchema } from "./telemetry.js"
import { modeConfigSchema } from "./mode.js"
import { customModePromptsSchema, customSupportPromptsSchema } from "./mode.js"
import { toolNamesSchema } from "./tool.js"
@@ -188,8 +187,6 @@ export const globalSettingsSchema = z.object({
language: languagesSchema.optional(),
- telemetrySetting: telemetrySettingsSchema.optional(),
-
mcpEnabled: z.boolean().optional(),
mode: z.string().optional(),
@@ -371,8 +368,6 @@ export const EVALS_SETTINGS: RooCodeSettings = {
maxDiagnosticMessages: 50,
language: "en",
- telemetrySetting: "enabled",
-
mcpEnabled: false,
mode: "code", // "architect",
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index cd5804aecb7..a50456f4490 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -23,7 +23,6 @@ export * from "./provider-settings.js"
export * from "./task.js"
export * from "./todo.js"
export * from "./skills.js"
-export * from "./telemetry.js"
export * from "./terminal.js"
export * from "./tool.js"
export * from "./tool-params.js"
diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts
index 56a75728980..7447dc772e7 100644
--- a/packages/types/src/task.ts
+++ b/packages/types/src/task.ts
@@ -4,7 +4,6 @@ import { RooCodeEventName } from "./events.js"
import type { RooCodeSettings } from "./global-settings.js"
import type { ClineMessage, QueuedMessage, TokenUsage } from "./message.js"
import type { ToolUsage, ToolName } from "./tool.js"
-import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js"
import type { TodoItem } from "./todo.js"
/**
@@ -36,10 +35,6 @@ export interface TaskProviderLike {
getProviderProfile(): Promise
setProviderProfile(providerProfile: string): Promise
- // Telemetry
- readonly appProperties: StaticAppProperties
- readonly gitProperties: GitProperties | undefined
- getTelemetryProperties(): Promise
readonly cwd: string
// Event Emitter
diff --git a/packages/types/src/telemetry.ts b/packages/types/src/telemetry.ts
deleted file mode 100644
index 68ed38fe326..00000000000
--- a/packages/types/src/telemetry.ts
+++ /dev/null
@@ -1,535 +0,0 @@
-import { z } from "zod"
-
-import { providerNames } from "./provider-settings.js"
-import { clineMessageSchema } from "./message.js"
-
-/**
- * TelemetrySetting
- */
-
-export const telemetrySettings = ["unset", "enabled", "disabled"] as const
-
-export const telemetrySettingsSchema = z.enum(telemetrySettings)
-
-export type TelemetrySetting = z.infer
-
-/**
- * TelemetryEventName
- */
-
-export enum TelemetryEventName {
- TASK_CREATED = "Task Created",
- TASK_RESTARTED = "Task Reopened",
- TASK_COMPLETED = "Task Completed",
- TASK_MESSAGE = "Task Message",
- TASK_CONVERSATION_MESSAGE = "Conversation Message",
- LLM_COMPLETION = "LLM Completion",
- MODE_SWITCH = "Mode Switched",
- MODE_SELECTOR_OPENED = "Mode Selector Opened",
- TOOL_USED = "Tool Used",
-
- CHECKPOINT_CREATED = "Checkpoint Created",
- CHECKPOINT_RESTORED = "Checkpoint Restored",
- CHECKPOINT_DIFFED = "Checkpoint Diffed",
-
- TAB_SHOWN = "Tab Shown",
- MODE_SETTINGS_CHANGED = "Mode Setting Changed",
- CUSTOM_MODE_CREATED = "Custom Mode Created",
-
- CONTEXT_CONDENSED = "Context Condensed",
- SLIDING_WINDOW_TRUNCATION = "Sliding Window Truncation",
-
- CODE_ACTION_USED = "Code Action Used",
- PROMPT_ENHANCED = "Prompt Enhanced",
-
- TITLE_BUTTON_CLICKED = "Title Button Clicked",
-
- AUTHENTICATION_INITIATED = "Authentication Initiated",
-
- MARKETPLACE_ITEM_INSTALLED = "Marketplace Item Installed",
- MARKETPLACE_ITEM_REMOVED = "Marketplace Item Removed",
- MARKETPLACE_TAB_VIEWED = "Marketplace Tab Viewed",
- MARKETPLACE_INSTALL_BUTTON_CLICKED = "Marketplace Install Button Clicked",
-
- SHARE_BUTTON_CLICKED = "Share Button Clicked",
- SHARE_ORGANIZATION_CLICKED = "Share Organization Clicked",
- SHARE_PUBLIC_CLICKED = "Share Public Clicked",
- SHARE_CONNECT_TO_CLOUD_CLICKED = "Share Connect To Cloud Clicked",
-
- ACCOUNT_CONNECT_CLICKED = "Account Connect Clicked",
- ACCOUNT_CONNECT_SUCCESS = "Account Connect Success",
- ACCOUNT_LOGOUT_CLICKED = "Account Logout Clicked",
- ACCOUNT_LOGOUT_SUCCESS = "Account Logout Success",
-
- FEATURED_PROVIDER_CLICKED = "Featured Provider Clicked",
-
- UPSELL_DISMISSED = "Upsell Dismissed",
- UPSELL_CLICKED = "Upsell Clicked",
-
- SCHEMA_VALIDATION_ERROR = "Schema Validation Error",
- DIFF_APPLICATION_ERROR = "Diff Application Error",
- SHELL_INTEGRATION_ERROR = "Shell Integration Error",
- CONSECUTIVE_MISTAKE_ERROR = "Consecutive Mistake Error",
- CODE_INDEX_ERROR = "Code Index Error",
- TELEMETRY_SETTINGS_CHANGED = "Telemetry Settings Changed",
- MODEL_CACHE_EMPTY_RESPONSE = "Model Cache Empty Response",
- READ_FILE_LEGACY_FORMAT_USED = "Read File Legacy Format Used",
-}
-
-/**
- * TelemetryProperties
- */
-
-export const staticAppPropertiesSchema = z.object({
- appName: z.string(),
- appVersion: z.string(),
- vscodeVersion: z.string(),
- platform: z.string(),
- editorName: z.string(),
- hostname: z.string().optional(),
-})
-
-export type StaticAppProperties = z.infer
-
-export const dynamicAppPropertiesSchema = z.object({
- language: z.string(),
- mode: z.string(),
-})
-
-export type DynamicAppProperties = z.infer
-
-export const cloudAppPropertiesSchema = z.object({
- cloudIsAuthenticated: z.boolean().optional(),
-})
-
-export type CloudAppProperties = z.infer
-
-export const appPropertiesSchema = z.object({
- ...staticAppPropertiesSchema.shape,
- ...dynamicAppPropertiesSchema.shape,
- ...cloudAppPropertiesSchema.shape,
-})
-
-export type AppProperties = z.infer
-
-export const taskPropertiesSchema = z.object({
- taskId: z.string().optional(),
- parentTaskId: z.string().optional(),
- apiProvider: z.enum(providerNames).optional(),
- modelId: z.string().optional(),
- diffStrategy: z.string().optional(),
- isSubtask: z.boolean().optional(),
- todos: z
- .object({
- total: z.number(),
- completed: z.number(),
- inProgress: z.number(),
- pending: z.number(),
- })
- .optional(),
-})
-
-export type TaskProperties = z.infer
-
-export const gitPropertiesSchema = z.object({
- repositoryUrl: z.string().optional(),
- repositoryName: z.string().optional(),
- defaultBranch: z.string().optional(),
-})
-
-export type GitProperties = z.infer
-
-export const telemetryPropertiesSchema = z.object({
- ...appPropertiesSchema.shape,
- ...taskPropertiesSchema.shape,
- ...gitPropertiesSchema.shape,
-})
-
-export type TelemetryProperties = z.infer
-
-/**
- * TelemetryEvent
- */
-
-export type TelemetryEvent = {
- event: TelemetryEventName
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- properties?: Record
-}
-
-/**
- * RooCodeTelemetryEvent
- */
-
-export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
- z.object({
- type: z.enum([
- TelemetryEventName.TASK_CREATED,
- TelemetryEventName.TASK_RESTARTED,
- TelemetryEventName.TASK_COMPLETED,
- TelemetryEventName.TASK_CONVERSATION_MESSAGE,
- TelemetryEventName.MODE_SWITCH,
- TelemetryEventName.MODE_SELECTOR_OPENED,
- TelemetryEventName.TOOL_USED,
- TelemetryEventName.CHECKPOINT_CREATED,
- TelemetryEventName.CHECKPOINT_RESTORED,
- TelemetryEventName.CHECKPOINT_DIFFED,
- TelemetryEventName.CODE_ACTION_USED,
- TelemetryEventName.PROMPT_ENHANCED,
- TelemetryEventName.TITLE_BUTTON_CLICKED,
- TelemetryEventName.AUTHENTICATION_INITIATED,
- TelemetryEventName.MARKETPLACE_ITEM_INSTALLED,
- TelemetryEventName.MARKETPLACE_ITEM_REMOVED,
- TelemetryEventName.MARKETPLACE_TAB_VIEWED,
- TelemetryEventName.MARKETPLACE_INSTALL_BUTTON_CLICKED,
- TelemetryEventName.SHARE_BUTTON_CLICKED,
- TelemetryEventName.SHARE_ORGANIZATION_CLICKED,
- TelemetryEventName.SHARE_PUBLIC_CLICKED,
- TelemetryEventName.SHARE_CONNECT_TO_CLOUD_CLICKED,
- TelemetryEventName.ACCOUNT_CONNECT_CLICKED,
- TelemetryEventName.ACCOUNT_CONNECT_SUCCESS,
- TelemetryEventName.ACCOUNT_LOGOUT_CLICKED,
- TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS,
- TelemetryEventName.FEATURED_PROVIDER_CLICKED,
- TelemetryEventName.UPSELL_DISMISSED,
- TelemetryEventName.UPSELL_CLICKED,
- TelemetryEventName.SCHEMA_VALIDATION_ERROR,
- TelemetryEventName.DIFF_APPLICATION_ERROR,
- TelemetryEventName.SHELL_INTEGRATION_ERROR,
- TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR,
- TelemetryEventName.CODE_INDEX_ERROR,
- TelemetryEventName.MODEL_CACHE_EMPTY_RESPONSE,
- TelemetryEventName.CONTEXT_CONDENSED,
- TelemetryEventName.SLIDING_WINDOW_TRUNCATION,
- TelemetryEventName.TAB_SHOWN,
- TelemetryEventName.MODE_SETTINGS_CHANGED,
- TelemetryEventName.CUSTOM_MODE_CREATED,
- TelemetryEventName.READ_FILE_LEGACY_FORMAT_USED,
- ]),
- properties: telemetryPropertiesSchema,
- }),
- z.object({
- type: z.literal(TelemetryEventName.TELEMETRY_SETTINGS_CHANGED),
- properties: z.object({
- ...telemetryPropertiesSchema.shape,
- previousSetting: telemetrySettingsSchema,
- newSetting: telemetrySettingsSchema,
- }),
- }),
- z.object({
- type: z.literal(TelemetryEventName.TASK_MESSAGE),
- properties: z.object({
- ...telemetryPropertiesSchema.shape,
- taskId: z.string(),
- message: clineMessageSchema,
- }),
- }),
- z.object({
- type: z.literal(TelemetryEventName.LLM_COMPLETION),
- properties: z.object({
- ...telemetryPropertiesSchema.shape,
- inputTokens: z.number(),
- outputTokens: z.number(),
- cacheReadTokens: z.number().optional(),
- cacheWriteTokens: z.number().optional(),
- cost: z.number().optional(),
- }),
- }),
-])
-
-export type RooCodeTelemetryEvent = z.infer
-
-/**
- * TelemetryEventSubscription
- */
-
-export type TelemetryEventSubscription =
- | { type: "include"; events: TelemetryEventName[] }
- | { type: "exclude"; events: TelemetryEventName[] }
-
-/**
- * TelemetryPropertiesProvider
- */
-
-export interface TelemetryPropertiesProvider {
- getTelemetryProperties(): Promise
-}
-
-/**
- * TelemetryClient
- */
-
-export interface TelemetryClient {
- subscription?: TelemetryEventSubscription
-
- setProvider(provider: TelemetryPropertiesProvider): void
- capture(options: TelemetryEvent): Promise
- captureException(error: Error, additionalProperties?: Record): Promise
- updateTelemetryState(isOptedIn: boolean): void
- isTelemetryEnabled(): boolean
- shutdown(): Promise
-}
-
-/**
- * Expected API error codes that should not be reported to telemetry.
- * These are normal/expected errors that users can't do much about.
- */
-export const EXPECTED_API_ERROR_CODES = new Set([
- 402, // Payment required - billing issues
- 429, // Rate limit - expected when hitting API limits
-])
-
-/**
- * Patterns in error messages that indicate expected errors (rate limits, etc.)
- * These are checked when no numeric error code is available.
- */
-const EXPECTED_ERROR_MESSAGE_PATTERNS = [
- /^429\b/, // Message starts with "429"
- /rate limit/i, // Contains "rate limit" (case insensitive)
-]
-
-/**
- * Interface representing the error structure from OpenAI SDK.
- * OpenAI SDK errors (APIError, AuthenticationError, RateLimitError, etc.)
- * have a numeric `status` property and may contain nested error metadata.
- *
- * @see https://github.com/openai/openai-node/blob/master/src/error.ts
- */
-interface OpenAISdkError {
- /** HTTP status code of the error response */
- status: number
- /** Optional error code (may be numeric or string) */
- code?: number | string
- /** Primary error message */
- message: string
- /** Nested error object containing additional details from the API response */
- error?: {
- message?: string
- metadata?: {
- /** Raw error message from upstream provider (e.g., OpenRouter upstream errors) */
- raw?: string
- }
- }
-}
-
-/**
- * Type guard to check if an error object is an OpenAI SDK error.
- * OpenAI SDK errors (APIError and subclasses) have: status, code, message properties.
- */
-function isOpenAISdkError(error: unknown): error is OpenAISdkError {
- return (
- typeof error === "object" &&
- error !== null &&
- "status" in error &&
- typeof (error as OpenAISdkError).status === "number"
- )
-}
-
-/**
- * Extracts the HTTP status code from an error object.
- * Supports OpenAI SDK errors that have a status property.
- * @param error - The error to extract status from
- * @returns The status code if available, undefined otherwise
- */
-export function getErrorStatusCode(error: unknown): number | undefined {
- if (isOpenAISdkError(error)) {
- return error.status
- }
- return undefined
-}
-
-/**
- * Extracts a message from a JSON payload embedded in an error string.
- * Handles cases like "503 {"error":{"message":"actual error message"}}"
- * or just '{"error":{"message":"actual error message"}}'
- *
- * @param message - The message string that may contain JSON
- * @returns The extracted message from the JSON payload, or undefined if not found
- */
-export function extractMessageFromJsonPayload(message: string): string | undefined {
- // Find the first occurrence of '{' which may indicate JSON content
- const jsonStartIndex = message.indexOf("{")
- if (jsonStartIndex === -1) {
- return undefined
- }
-
- const potentialJson = message.slice(jsonStartIndex)
-
- try {
- const parsed = JSON.parse(potentialJson)
-
- // Handle structure: {"error":{"message":"..."}} or {"error":{"code":"","message":"..."}}
- if (parsed?.error?.message && typeof parsed.error.message === "string") {
- return parsed.error.message
- }
-
- // Handle structure: {"message":"..."}
- if (parsed?.message && typeof parsed.message === "string") {
- return parsed.message
- }
- } catch {
- // JSON parsing failed - not valid JSON
- }
-
- return undefined
-}
-
-/**
- * Extracts the most descriptive error message from an error object.
- * Prioritizes nested metadata (upstream provider errors) over the standard message.
- * Also handles JSON payloads embedded in error messages.
- * @param error - The error to extract message from
- * @returns The best available error message, or undefined if not extractable
- */
-export function getErrorMessage(error: unknown): string | undefined {
- let message: string | undefined
-
- if (isOpenAISdkError(error)) {
- // Prioritize nested metadata which may contain upstream provider details
- message = error.error?.metadata?.raw || error.error?.message || error.message
- } else if (error instanceof Error) {
- // Handle standard Error objects (including ApiProviderError)
- message = error.message
- } else if (typeof error === "object" && error !== null && "message" in error) {
- // Handle plain objects with a message property
- const msgValue = (error as { message: unknown }).message
- if (typeof msgValue === "string") {
- message = msgValue
- }
- }
-
- if (!message) {
- return undefined
- }
-
- // If the message contains JSON, try to extract the message from it
- const extractedMessage = extractMessageFromJsonPayload(message)
- if (extractedMessage) {
- return extractedMessage
- }
-
- return message
-}
-
-/**
- * Helper to check if an API error should be reported to telemetry.
- * Filters out expected errors like rate limits by checking both error codes and messages.
- * @param errorCode - The HTTP error code (if available)
- * @param errorMessage - The error message (if available)
- * @returns true if the error should be reported, false if it should be filtered out
- */
-export function shouldReportApiErrorToTelemetry(errorCode?: number, errorMessage?: string): boolean {
- // Check numeric error code
- if (errorCode !== undefined && EXPECTED_API_ERROR_CODES.has(errorCode)) {
- return false
- }
-
- // Check error message for expected patterns (e.g., "429 Rate limit exceeded")
- if (errorMessage) {
- for (const pattern of EXPECTED_ERROR_MESSAGE_PATTERNS) {
- if (pattern.test(errorMessage)) {
- return false
- }
- }
- }
-
- return true
-}
-
-/**
- * Generic API provider error class for structured error tracking via PostHog.
- * Can be reused by any API provider.
- */
-export class ApiProviderError extends Error {
- constructor(
- message: string,
- public readonly provider: string,
- public readonly modelId: string,
- public readonly operation: string,
- public readonly errorCode?: number,
- ) {
- super(message)
- this.name = "ApiProviderError"
- }
-}
-
-/**
- * Type guard to check if an error is an ApiProviderError.
- * Used by telemetry to automatically extract structured properties.
- */
-export function isApiProviderError(error: unknown): error is ApiProviderError {
- return (
- error instanceof Error &&
- error.name === "ApiProviderError" &&
- "provider" in error &&
- "modelId" in error &&
- "operation" in error
- )
-}
-
-/**
- * Extracts properties from an ApiProviderError for telemetry.
- * Returns the structured properties that can be merged with additionalProperties.
- */
-export function extractApiProviderErrorProperties(error: ApiProviderError): Record {
- return {
- provider: error.provider,
- modelId: error.modelId,
- operation: error.operation,
- ...(error.errorCode !== undefined && { errorCode: error.errorCode }),
- }
-}
-
-/**
- * Reason why the consecutive mistake limit was reached.
- */
-export type ConsecutiveMistakeReason = "no_tools_used" | "tool_repetition" | "unknown"
-
-/**
- * Error class for "Roo is having trouble" consecutive mistake scenarios.
- * Triggered when the task reaches the configured consecutive mistake limit.
- * Used for structured exception tracking via PostHog.
- */
-export class ConsecutiveMistakeError extends Error {
- constructor(
- message: string,
- public readonly taskId: string,
- public readonly consecutiveMistakeCount: number,
- public readonly consecutiveMistakeLimit: number,
- public readonly reason: ConsecutiveMistakeReason = "unknown",
- public readonly provider?: string,
- public readonly modelId?: string,
- ) {
- super(message)
- this.name = "ConsecutiveMistakeError"
- }
-}
-
-/**
- * Type guard to check if an error is a ConsecutiveMistakeError.
- * Used by telemetry to automatically extract structured properties.
- */
-export function isConsecutiveMistakeError(error: unknown): error is ConsecutiveMistakeError {
- return (
- error instanceof Error &&
- error.name === "ConsecutiveMistakeError" &&
- "taskId" in error &&
- "consecutiveMistakeCount" in error &&
- "consecutiveMistakeLimit" in error
- )
-}
-
-/**
- * Extracts properties from a ConsecutiveMistakeError for telemetry.
- * Returns the structured properties that can be merged with additionalProperties.
- */
-export function extractConsecutiveMistakeErrorProperties(error: ConsecutiveMistakeError): Record {
- return {
- taskId: error.taskId,
- consecutiveMistakeCount: error.consecutiveMistakeCount,
- consecutiveMistakeLimit: error.consecutiveMistakeLimit,
- reason: error.reason,
- ...(error.provider !== undefined && { provider: error.provider }),
- ...(error.modelId !== undefined && { modelId: error.modelId }),
- }
-}
diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts
index f006cfba1df..cb4699013fc 100644
--- a/packages/types/src/vscode-extension-host.ts
+++ b/packages/types/src/vscode-extension-host.ts
@@ -4,7 +4,6 @@ import type { GlobalSettings, RooCodeSettings } from "./global-settings.js"
import type { ProviderSettings, ProviderSettingsEntry } from "./provider-settings.js"
import type { HistoryItem } from "./history.js"
import type { ModeConfig, PromptComponent } from "./mode.js"
-import type { TelemetrySetting } from "./telemetry.js"
import type { Experiments } from "./experiment.js"
import type { ClineMessage, QueuedMessage } from "./message.js"
import {
@@ -340,10 +339,6 @@ export type ExtensionState = Pick<
toolRequirements?: Record // Map of tool names to their requirements (e.g. {"apply_diff": true})
cwd?: string // Current working directory
- telemetrySetting: TelemetrySetting
- telemetryKey?: string
- machineId?: string
-
renderContext: "sidebar" | "editor"
settingsImportedAt?: number
historyPreviewCollapsed?: boolean
@@ -493,7 +488,6 @@ export interface WebviewMessage {
| "checkpointRestore"
| "deleteMcpServer"
| "codebaseIndexEnabled"
- | "telemetrySetting"
| "searchFiles"
| "toggleApiConfigPin"
| "hasOpenedModeSelector"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 930b53409c2..bccb42dae51 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -388,9 +388,6 @@ importers:
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- posthog-js:
- specifier: ^1.336.4
- version: 1.336.4
react:
specifier: ^18.3.1
version: 18.3.1
@@ -679,34 +676,6 @@ importers:
specifier: ^3.2.3
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- packages/telemetry:
- dependencies:
- '@roo-code/types':
- specifier: workspace:^
- version: link:../types
- posthog-node:
- specifier: ^5.0.0
- version: 5.1.1
- zod:
- specifier: 3.25.76
- version: 3.25.76
- devDependencies:
- '@roo-code/config-eslint':
- specifier: workspace:^
- version: link:../config-eslint
- '@roo-code/config-typescript':
- specifier: workspace:^
- version: link:../config-typescript
- '@types/node':
- specifier: 20.x
- version: 20.17.57
- '@types/vscode':
- specifier: ^1.84.0
- version: 1.100.0
- vitest:
- specifier: ^3.2.3
- version: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.57)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
-
packages/types:
dependencies:
ai-sdk-provider-poe:
@@ -818,9 +787,6 @@ importers:
'@roo-code/ipc':
specifier: workspace:^
version: link:../packages/ipc
- '@roo-code/telemetry':
- specifier: workspace:^
- version: link:../packages/telemetry
'@roo-code/types':
specifier: workspace:^
version: link:../packages/types
@@ -1266,9 +1232,6 @@ importers:
mermaid:
specifier: ^11.4.1
version: 11.10.0
- posthog-js:
- specifier: ^1.227.2
- version: 1.242.1
pretty-bytes:
specifier: ^7.0.0
version: 7.0.0
@@ -2877,78 +2840,10 @@ packages:
ai: ^6.0.0
zod: 3.25.76
- '@opentelemetry/api-logs@0.208.0':
- resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==}
- engines: {node: '>=8.0.0'}
-
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
- '@opentelemetry/core@2.2.0':
- resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.0.0 <1.10.0'
-
- '@opentelemetry/core@2.5.0':
- resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.0.0 <1.10.0'
-
- '@opentelemetry/exporter-logs-otlp-http@0.208.0':
- resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': ^1.3.0
-
- '@opentelemetry/otlp-exporter-base@0.208.0':
- resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': ^1.3.0
-
- '@opentelemetry/otlp-transformer@0.208.0':
- resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': ^1.3.0
-
- '@opentelemetry/resources@2.2.0':
- resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.3.0 <1.10.0'
-
- '@opentelemetry/resources@2.5.0':
- resolution: {integrity: sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.3.0 <1.10.0'
-
- '@opentelemetry/sdk-logs@0.208.0':
- resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.4.0 <1.10.0'
-
- '@opentelemetry/sdk-metrics@2.2.0':
- resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.9.0 <1.10.0'
-
- '@opentelemetry/sdk-trace-base@2.2.0':
- resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==}
- engines: {node: ^18.19.0 || >=20.6.0}
- peerDependencies:
- '@opentelemetry/api': '>=1.3.0 <1.10.0'
-
- '@opentelemetry/semantic-conventions@1.39.0':
- resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==}
- engines: {node: '>=14'}
-
'@oxc-resolver/binding-darwin-arm64@11.2.0':
resolution: {integrity: sha512-ruKLkS+Dm/YIJaUhzEB7zPI+jh3EXxu0QnNV8I7t9jf0lpD2VnltuyRbhrbJEkksklZj//xCMyFFsILGjiU2Mg==}
cpu: [arm64]
@@ -3020,42 +2915,6 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
- '@posthog/core@1.17.0':
- resolution: {integrity: sha512-8pDNL+/u9ojzXloA5wILVDXBCV5daJ7w2ipCALQlEEZmL752cCKhRpbyiHn3tjKXh3Hy6aOboJneYa1JdlVHrQ==}
-
- '@posthog/types@1.336.4':
- resolution: {integrity: sha512-BY3cq/8segbXEvHbEXx9SWmaKJEM0AGgsOgMFH2yy13AV+rUHsGcp4Z5LDI5pU25DURN9EAZvzcoVyYy/Iokmw==}
-
- '@protobufjs/aspromise@1.1.2':
- resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
-
- '@protobufjs/base64@1.1.2':
- resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
-
- '@protobufjs/codegen@2.0.4':
- resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
-
- '@protobufjs/eventemitter@1.1.0':
- resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
-
- '@protobufjs/fetch@1.1.0':
- resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
-
- '@protobufjs/float@1.0.2':
- resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
-
- '@protobufjs/inquire@1.1.0':
- resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
-
- '@protobufjs/path@1.1.2':
- resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
-
- '@protobufjs/pool@1.1.0':
- resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
-
- '@protobufjs/utf8@1.1.0':
- resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
-
'@puppeteer/browsers@2.10.5':
resolution: {integrity: sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==}
engines: {node: '>=18'}
@@ -5625,9 +5484,6 @@ packages:
resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==}
hasBin: true
- core-js@3.42.0:
- resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==}
-
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@@ -6087,9 +5943,6 @@ packages:
dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
- dompurify@3.3.1:
- resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==}
-
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@@ -6690,9 +6543,6 @@ packages:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
- fflate@0.4.8:
- resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
-
fflate@0.7.4:
resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
@@ -8040,9 +7890,6 @@ packages:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
- long@5.3.2:
- resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
-
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -9012,30 +8859,6 @@ packages:
resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==}
engines: {node: '>=12'}
- posthog-js@1.242.1:
- resolution: {integrity: sha512-j2mzw0eukyuw/Qm3tNZ6pfaXmc7eglWj6ftmvR1Lz9GtMr85ndGNXJvIGO+6PBrQW2o0D1G0k/KV93ehta0hFA==}
- peerDependencies:
- '@rrweb/types': 2.0.0-alpha.17
- rrweb-snapshot: 2.0.0-alpha.17
- peerDependenciesMeta:
- '@rrweb/types':
- optional: true
- rrweb-snapshot:
- optional: true
-
- posthog-js@1.336.4:
- resolution: {integrity: sha512-NX81XaqOjS/gue3UsbAAuJxi6vD0AGy1HUvywBIhAArCwbTXKS04NhEFwUcYJdrmwXUf94MntEIWGoc1pTFDtg==}
-
- posthog-node@5.1.1:
- resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==}
- engines: {node: '>=20'}
-
- preact@10.26.6:
- resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==}
-
- preact@10.28.2:
- resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==}
-
prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'}
@@ -9105,10 +8928,6 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
- protobufjs@7.5.4:
- resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
- engines: {node: '>=12.0.0'}
-
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -9154,9 +8973,6 @@ packages:
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
- query-selector-shadow-dom@1.0.1:
- resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==}
-
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -10833,12 +10649,6 @@ packages:
web-tree-sitter@0.25.6:
resolution: {integrity: sha512-WG+/YGbxw8r+rLlzzhV+OvgiOJCWdIpOucG3qBf3RCBFMkGDb1CanUi2BxCxjnkpzU3/hLWPT8VO5EKsMk9Fxg==}
- web-vitals@4.2.4:
- resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
-
- web-vitals@5.1.0:
- resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==}
-
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
@@ -12999,82 +12809,8 @@ snapshots:
ai: 6.0.77(zod@3.25.76)
zod: 3.25.76
- '@opentelemetry/api-logs@0.208.0':
- dependencies:
- '@opentelemetry/api': 1.9.0
-
'@opentelemetry/api@1.9.0': {}
- '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/semantic-conventions': 1.39.0
-
- '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/semantic-conventions': 1.39.0
-
- '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.208.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0)
-
- '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0)
-
- '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.208.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0)
- protobufjs: 7.5.4
-
- '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.39.0
-
- '@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.39.0
-
- '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.208.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0)
-
- '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0)
-
- '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)':
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/semantic-conventions': 1.39.0
-
- '@opentelemetry/semantic-conventions@1.39.0': {}
-
'@oxc-resolver/binding-darwin-arm64@11.2.0':
optional: true
@@ -13121,35 +12857,6 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
- '@posthog/core@1.17.0':
- dependencies:
- cross-spawn: 7.0.6
-
- '@posthog/types@1.336.4': {}
-
- '@protobufjs/aspromise@1.1.2': {}
-
- '@protobufjs/base64@1.1.2': {}
-
- '@protobufjs/codegen@2.0.4': {}
-
- '@protobufjs/eventemitter@1.1.0': {}
-
- '@protobufjs/fetch@1.1.0':
- dependencies:
- '@protobufjs/aspromise': 1.1.2
- '@protobufjs/inquire': 1.1.0
-
- '@protobufjs/float@1.0.2': {}
-
- '@protobufjs/inquire@1.1.0': {}
-
- '@protobufjs/path@1.1.2': {}
-
- '@protobufjs/pool@1.1.0': {}
-
- '@protobufjs/utf8@1.1.0': {}
-
'@puppeteer/browsers@2.10.5':
dependencies:
debug: 4.4.1(supports-color@8.1.1)
@@ -16008,8 +15715,6 @@ snapshots:
untildify: 4.0.0
yargs: 16.2.0
- core-js@3.42.0: {}
-
core-util-is@1.0.3: {}
cors@2.8.5:
@@ -16458,10 +16163,6 @@ snapshots:
optionalDependencies:
'@types/trusted-types': 2.0.7
- dompurify@3.3.1:
- optionalDependencies:
- '@types/trusted-types': 2.0.7
-
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
@@ -17172,8 +16873,6 @@ snapshots:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
- fflate@0.4.8: {}
-
fflate@0.7.4: {}
fflate@0.8.2: {}
@@ -18631,8 +18330,6 @@ snapshots:
strip-ansi: 7.1.2
wrap-ansi: 9.0.0
- long@5.3.2: {}
-
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@@ -19877,35 +19574,6 @@ snapshots:
postgres@3.4.7: {}
- posthog-js@1.242.1:
- dependencies:
- core-js: 3.42.0
- fflate: 0.4.8
- preact: 10.26.6
- web-vitals: 4.2.4
-
- posthog-js@1.336.4:
- dependencies:
- '@opentelemetry/api': 1.9.0
- '@opentelemetry/api-logs': 0.208.0
- '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0)
- '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0)
- '@posthog/core': 1.17.0
- '@posthog/types': 1.336.4
- core-js: 3.42.0
- dompurify: 3.3.1
- fflate: 0.4.8
- preact: 10.28.2
- query-selector-shadow-dom: 1.0.1
- web-vitals: 5.1.0
-
- posthog-node@5.1.1: {}
-
- preact@10.26.6: {}
-
- preact@10.28.2: {}
-
prebuild-install@7.1.3:
dependencies:
detect-libc: 2.0.4
@@ -19979,21 +19647,6 @@ snapshots:
property-information@7.1.0: {}
- protobufjs@7.5.4:
- dependencies:
- '@protobufjs/aspromise': 1.1.2
- '@protobufjs/base64': 1.1.2
- '@protobufjs/codegen': 2.0.4
- '@protobufjs/eventemitter': 1.1.0
- '@protobufjs/fetch': 1.1.0
- '@protobufjs/float': 1.0.2
- '@protobufjs/inquire': 1.1.0
- '@protobufjs/path': 1.1.2
- '@protobufjs/pool': 1.1.0
- '@protobufjs/utf8': 1.1.0
- '@types/node': 24.2.1
- long: 5.3.2
-
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -20073,8 +19726,6 @@ snapshots:
quansync@0.2.11: {}
- query-selector-shadow-dom@1.0.1: {}
-
queue-microtask@1.2.3: {}
randombytes@2.1.0:
@@ -22181,10 +21832,6 @@ snapshots:
web-tree-sitter@0.25.6: {}
- web-vitals@4.2.4: {}
-
- web-vitals@5.1.0: {}
-
webidl-conversions@3.0.1: {}
webidl-conversions@4.0.2: {}
diff --git a/src/__tests__/extension.spec.ts b/src/__tests__/extension.spec.ts
index c6c44b13fc4..99c0641f3a7 100644
--- a/src/__tests__/extension.spec.ts
+++ b/src/__tests__/extension.spec.ts
@@ -72,24 +72,6 @@ vi.mock("@roo-code/cloud", () => ({
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- createInstance: vi.fn().mockReturnValue({
- register: vi.fn(),
- setProvider: vi.fn(),
- shutdown: vi.fn(),
- }),
- get instance() {
- return {
- register: vi.fn(),
- setProvider: vi.fn(),
- shutdown: vi.fn(),
- }
- },
- },
- PostHogTelemetryClient: vi.fn(),
-}))
-
vi.mock("../utils/outputChannelLogger", () => ({
createOutputChannelLogger: vi.fn().mockReturnValue(vi.fn()),
createDualLogger: vi.fn().mockReturnValue(vi.fn()),
@@ -276,7 +258,6 @@ describe("extension.ts", () => {
return {
off: vi.fn(),
on: vi.fn(),
- telemetryClient: null,
authService: mockAuthService,
hasActiveSession: vi.fn().mockReturnValue(false),
} as any
@@ -316,7 +297,6 @@ describe("extension.ts", () => {
return {
off: vi.fn(),
on: vi.fn(),
- telemetryClient: null,
authService: null,
hasActiveSession: vi.fn().mockReturnValue(false),
} as any
diff --git a/src/__tests__/nested-delegation-resume.spec.ts b/src/__tests__/nested-delegation-resume.spec.ts
index 5dbafc949cb..fac9a7bcade 100644
--- a/src/__tests__/nested-delegation-resume.spec.ts
+++ b/src/__tests__/nested-delegation-resume.spec.ts
@@ -4,18 +4,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"
import { RooCodeEventName } from "@roo-code/types"
// Mock safe-stable-stringify to avoid runtime error
-vi.mock("safe-stable-stringify", () => ({
- default: (obj: any) => JSON.stringify(obj),
-}))
-
-// Mock TelemetryService
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCompleted: vi.fn(),
- },
- },
-}))
// vscode mock for Task/Provider imports
vi.mock("vscode", () => {
diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts
index e3056a8206a..8b86281f9d5 100644
--- a/src/activate/registerCommands.ts
+++ b/src/activate/registerCommands.ts
@@ -2,7 +2,6 @@ import * as vscode from "vscode"
import delay from "delay"
import type { CommandId } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Package } from "../shared/package"
import { getCommand } from "../utils/commands"
@@ -78,8 +77,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
return
}
- TelemetryService.instance.captureTitleButtonClicked("cloud")
-
visibleProvider.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
},
plusButtonClicked: async () => {
@@ -89,8 +86,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
return
}
- TelemetryService.instance.captureTitleButtonClicked("plus")
-
await visibleProvider.removeClineFromStack()
await visibleProvider.refreshWorkspace()
await visibleProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
@@ -99,8 +94,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
await visibleProvider.postMessageToWebview({ type: "action", action: "focusInput" })
},
popoutButtonClicked: () => {
- TelemetryService.instance.captureTitleButtonClicked("popout")
-
return openClineInNewTab({ context, outputChannel })
},
openInNewTab: () => openClineInNewTab({ context, outputChannel }),
@@ -111,8 +104,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
return
}
- TelemetryService.instance.captureTitleButtonClicked("settings")
-
visibleProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
// Also explicitly post the visibility message to trigger scroll reliably
visibleProvider.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
@@ -124,8 +115,6 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
return
}
- TelemetryService.instance.captureTitleButtonClicked("history")
-
visibleProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
},
marketplaceButtonClicked: () => {
diff --git a/src/api/providers/__tests__/anthropic.spec.ts b/src/api/providers/__tests__/anthropic.spec.ts
index 3731f3a068b..358b1f02943 100644
--- a/src/api/providers/__tests__/anthropic.spec.ts
+++ b/src/api/providers/__tests__/anthropic.spec.ts
@@ -3,15 +3,6 @@
import { AnthropicHandler } from "../anthropic"
import { ApiHandlerOptions } from "../../../shared/api"
-// Mock TelemetryService
-vitest.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: vitest.fn(),
- },
- },
-}))
-
const mockCreate = vitest.fn()
vitest.mock("@anthropic-ai/sdk", () => {
diff --git a/src/api/providers/__tests__/bedrock-error-handling.spec.ts b/src/api/providers/__tests__/bedrock-error-handling.spec.ts
index 2041dde4577..89ddd9fb12f 100644
--- a/src/api/providers/__tests__/bedrock-error-handling.spec.ts
+++ b/src/api/providers/__tests__/bedrock-error-handling.spec.ts
@@ -1,12 +1,4 @@
-// Mock TelemetryService - must come before other imports
const mockCaptureException = vi.hoisted(() => vi.fn())
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: mockCaptureException,
- },
- },
-}))
// Mock BedrockRuntimeClient and commands
const mockSend = vi.fn()
diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts
index 975e38af123..ca96c606786 100644
--- a/src/api/providers/__tests__/bedrock.spec.ts
+++ b/src/api/providers/__tests__/bedrock.spec.ts
@@ -1,14 +1,3 @@
-// Mock TelemetryService before other imports
-const mockCaptureException = vi.fn()
-
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: (...args: unknown[]) => mockCaptureException(...args),
- },
- },
-}))
-
// Mock AWS SDK credential providers
vi.mock("@aws-sdk/credential-providers", () => {
const mockFromIni = vi.fn().mockReturnValue({
@@ -36,12 +25,7 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
import { AwsBedrockHandler } from "../bedrock"
import { ConverseStreamCommand, BedrockRuntimeClient, ConverseCommand } from "@aws-sdk/client-bedrock-runtime"
-import {
- BEDROCK_1M_CONTEXT_MODEL_IDS,
- BEDROCK_SERVICE_TIER_MODEL_IDS,
- bedrockModels,
- ApiProviderError,
-} from "@roo-code/types"
+import { BEDROCK_1M_CONTEXT_MODEL_IDS, BEDROCK_SERVICE_TIER_MODEL_IDS, bedrockModels } from "@roo-code/types"
import type { Anthropic } from "@anthropic-ai/sdk"
@@ -1141,141 +1125,6 @@ describe("AwsBedrockHandler", () => {
})
})
- describe("error telemetry", () => {
- let mockSend: ReturnType
-
- beforeEach(() => {
- mockCaptureException.mockClear()
- // Get access to the mock send function from the mocked client
- mockSend = vi.mocked(BedrockRuntimeClient).mock.results[0]?.value?.send
- })
-
- it("should capture telemetry on createMessage error", async () => {
- // Create a handler with a fresh mock
- const errorHandler = new AwsBedrockHandler({
- apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
- awsAccessKey: "test-access-key",
- awsSecretKey: "test-secret-key",
- awsRegion: "us-east-1",
- })
-
- // Get the mock send from the new handler instance
- const clientInstance =
- vi.mocked(BedrockRuntimeClient).mock.results[vi.mocked(BedrockRuntimeClient).mock.results.length - 1]
- ?.value
- const mockSendFn = clientInstance?.send as ReturnType
-
- // Mock the send to throw an error
- mockSendFn.mockRejectedValueOnce(new Error("Bedrock API error"))
-
- const messages: Anthropic.Messages.MessageParam[] = [
- {
- role: "user",
- content: "Hello",
- },
- ]
-
- const generator = errorHandler.createMessage("You are a helpful assistant", messages)
-
- // Consume the generator - it should throw
- await expect(async () => {
- for await (const _chunk of generator) {
- // Should throw before or during iteration
- }
- }).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Bedrock API error",
- provider: "Bedrock",
- modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
- operation: "createMessage",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should capture telemetry on completePrompt error", async () => {
- // Create a handler with a fresh mock
- const errorHandler = new AwsBedrockHandler({
- apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
- awsAccessKey: "test-access-key",
- awsSecretKey: "test-secret-key",
- awsRegion: "us-east-1",
- })
-
- // Get the mock send from the new handler instance
- const clientInstance =
- vi.mocked(BedrockRuntimeClient).mock.results[vi.mocked(BedrockRuntimeClient).mock.results.length - 1]
- ?.value
- const mockSendFn = clientInstance?.send as ReturnType
-
- // Mock the send to throw an error for ConverseCommand
- mockSendFn.mockRejectedValueOnce(new Error("Bedrock completion error"))
-
- // Call completePrompt - it should throw
- await expect(errorHandler.completePrompt("Test prompt")).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Bedrock completion error",
- provider: "Bedrock",
- modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
- operation: "completePrompt",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should still throw the error after capturing telemetry", async () => {
- // Create a handler with a fresh mock
- const errorHandler = new AwsBedrockHandler({
- apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
- awsAccessKey: "test-access-key",
- awsSecretKey: "test-secret-key",
- awsRegion: "us-east-1",
- })
-
- // Get the mock send from the new handler instance
- const clientInstance =
- vi.mocked(BedrockRuntimeClient).mock.results[vi.mocked(BedrockRuntimeClient).mock.results.length - 1]
- ?.value
- const mockSendFn = clientInstance?.send as ReturnType
-
- // Mock the send to throw an error
- mockSendFn.mockRejectedValueOnce(new Error("Test error for throw verification"))
-
- const messages: Anthropic.Messages.MessageParam[] = [
- {
- role: "user",
- content: "Hello",
- },
- ]
-
- const generator = errorHandler.createMessage("You are a helpful assistant", messages)
-
- // Verify the error is still thrown after telemetry capture
- await expect(async () => {
- for await (const _chunk of generator) {
- // Should throw
- }
- }).rejects.toThrow()
-
- // Telemetry should have been captured before the error was thrown
- expect(mockCaptureException).toHaveBeenCalled()
- })
- })
-
describe("prompt cache default behavior", () => {
beforeEach(() => {
mockConverseStreamCommand.mockReset()
diff --git a/src/api/providers/__tests__/gemini.spec.ts b/src/api/providers/__tests__/gemini.spec.ts
index 47ee79dd0d6..02781d8611f 100644
--- a/src/api/providers/__tests__/gemini.spec.ts
+++ b/src/api/providers/__tests__/gemini.spec.ts
@@ -1,18 +1,8 @@
// npx vitest run src/api/providers/__tests__/gemini.spec.ts
-const mockCaptureException = vitest.fn()
-
-vitest.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: (...args: unknown[]) => mockCaptureException(...args),
- },
- },
-}))
-
import { Anthropic } from "@anthropic-ai/sdk"
-import { type ModelInfo, geminiDefaultModelId, ApiProviderError } from "@roo-code/types"
+import { type ModelInfo, geminiDefaultModelId } from "@roo-code/types"
import { t } from "i18next"
import { GeminiHandler } from "../gemini"
@@ -24,7 +14,6 @@ describe("GeminiHandler", () => {
beforeEach(() => {
// Reset mocks
- mockCaptureException.mockClear()
// Create mock functions
const mockGenerateContentStream = vitest.fn()
@@ -256,82 +245,4 @@ describe("GeminiHandler", () => {
expect(cost).toBeUndefined()
})
})
-
- describe("error telemetry", () => {
- const mockMessages: Anthropic.Messages.MessageParam[] = [
- {
- role: "user",
- content: "Hello",
- },
- ]
-
- const systemPrompt = "You are a helpful assistant"
-
- it("should capture telemetry on createMessage error", async () => {
- const mockError = new Error("Gemini API error")
- ;(handler["client"].models.generateContentStream as any).mockRejectedValue(mockError)
-
- const stream = handler.createMessage(systemPrompt, mockMessages)
-
- await expect(async () => {
- for await (const _chunk of stream) {
- // Should throw before yielding any chunks
- }
- }).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Gemini API error",
- provider: "Gemini",
- modelId: GEMINI_MODEL_NAME,
- operation: "createMessage",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should capture telemetry on completePrompt error", async () => {
- const mockError = new Error("Gemini completion error")
- ;(handler["client"].models.generateContent as any).mockRejectedValue(mockError)
-
- await expect(handler.completePrompt("Test prompt")).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Gemini completion error",
- provider: "Gemini",
- modelId: GEMINI_MODEL_NAME,
- operation: "completePrompt",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should still throw the error after capturing telemetry", async () => {
- const mockError = new Error("Gemini API error")
- ;(handler["client"].models.generateContentStream as any).mockRejectedValue(mockError)
-
- const stream = handler.createMessage(systemPrompt, mockMessages)
-
- // Verify the error is still thrown
- await expect(async () => {
- for await (const _chunk of stream) {
- // Should throw
- }
- }).rejects.toThrow()
-
- // Telemetry should have been captured before the error was thrown
- expect(mockCaptureException).toHaveBeenCalled()
- })
- })
})
diff --git a/src/api/providers/__tests__/mistral.spec.ts b/src/api/providers/__tests__/mistral.spec.ts
index 28aae09658e..f5238e1e35c 100644
--- a/src/api/providers/__tests__/mistral.spec.ts
+++ b/src/api/providers/__tests__/mistral.spec.ts
@@ -1,12 +1,4 @@
-// Mock TelemetryService - must come before other imports
const mockCaptureException = vi.hoisted(() => vi.fn())
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: mockCaptureException,
- },
- },
-}))
// Mock Mistral client - must come before other imports
const mockCreate = vi.fn()
diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts
index 6887da4d20f..60668d7b633 100644
--- a/src/api/providers/__tests__/openai-native.spec.ts
+++ b/src/api/providers/__tests__/openai-native.spec.ts
@@ -1,19 +1,9 @@
// npx vitest run api/providers/__tests__/openai-native.spec.ts
-const mockCaptureException = vitest.fn()
-
-vitest.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: (...args: unknown[]) => mockCaptureException(...args),
- },
- },
-}))
-
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
-import { ApiProviderError } from "@roo-code/types"
+import {} from "@roo-code/types"
import { OpenAiNativeHandler } from "../openai-native"
import { ApiHandlerOptions } from "../../../shared/api"
@@ -50,7 +40,6 @@ describe("OpenAiNativeHandler", () => {
}
handler = new OpenAiNativeHandler(mockOptions)
mockResponsesCreate.mockClear()
- mockCaptureException.mockClear()
// Clear fetch mock if it exists
if ((global as any).fetch) {
delete (global as any).fetch
@@ -1113,150 +1102,6 @@ describe("OpenAiNativeHandler", () => {
}
})
})
-
- describe("error telemetry", () => {
- const errorMessages: Anthropic.Messages.MessageParam[] = [
- {
- role: "user",
- content: "Hello",
- },
- ]
-
- const errorSystemPrompt = "You are a helpful assistant"
-
- beforeEach(() => {
- mockCaptureException.mockClear()
- })
-
- it("should capture telemetry on createMessage error", async () => {
- // Mock fetch to return error
- const mockFetch = vitest.fn().mockResolvedValue({
- ok: false,
- status: 500,
- text: async () => "Internal Server Error",
- })
- global.fetch = mockFetch as any
-
- // Mock SDK to fail so it falls back to fetch
- mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
-
- const stream = handler.createMessage(errorSystemPrompt, errorMessages)
-
- await expect(async () => {
- for await (const _chunk of stream) {
- // Should throw before yielding any chunks
- }
- }).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: expect.stringContaining("OpenAI service error"),
- provider: "OpenAI Native",
- modelId: "gpt-4.1",
- operation: "createMessage",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should capture telemetry on stream processing error", async () => {
- // Mock fetch to return a stream with an error event
- const mockFetch = vitest.fn().mockResolvedValue({
- ok: true,
- body: new ReadableStream({
- start(controller) {
- controller.enqueue(
- new TextEncoder().encode(
- 'data: {"type":"response.error","error":{"message":"Model overloaded"}}\n\n',
- ),
- )
- controller.close()
- },
- }),
- })
- global.fetch = mockFetch as any
-
- // Mock SDK to fail so it falls back to fetch
- mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
-
- const stream = handler.createMessage(errorSystemPrompt, errorMessages)
-
- await expect(async () => {
- for await (const _chunk of stream) {
- // Should throw when encountering error event
- }
- }).rejects.toThrow()
-
- // Verify telemetry was captured (may be called multiple times due to error propagation)
- expect(mockCaptureException).toHaveBeenCalled()
-
- // Find the call with the stream error message
- const streamErrorCall = mockCaptureException.mock.calls.find((call: any[]) =>
- call[0]?.message?.includes("Model overloaded"),
- )
- expect(streamErrorCall).toBeDefined()
- expect(streamErrorCall![0]).toMatchObject({
- provider: "OpenAI Native",
- modelId: "gpt-4.1",
- operation: "createMessage",
- })
-
- // Verify it's an ApiProviderError
- expect(streamErrorCall![0]).toBeInstanceOf(ApiProviderError)
- })
-
- it("should capture telemetry on completePrompt error", async () => {
- // Mock SDK to throw an error
- mockResponsesCreate.mockRejectedValue(new Error("API Error"))
-
- await expect(handler.completePrompt("Test prompt")).rejects.toThrow()
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledTimes(1)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "API Error",
- provider: "OpenAI Native",
- modelId: "gpt-4.1",
- operation: "completePrompt",
- }),
- )
-
- // Verify it's an ApiProviderError
- const capturedError = mockCaptureException.mock.calls[0][0]
- expect(capturedError).toBeInstanceOf(ApiProviderError)
- })
-
- it("should still throw the error after capturing telemetry", async () => {
- // Mock fetch to return error
- const mockFetch = vitest.fn().mockResolvedValue({
- ok: false,
- status: 500,
- text: async () => "Internal Server Error",
- })
- global.fetch = mockFetch as any
-
- // Mock SDK to fail
- mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
-
- const stream = handler.createMessage(errorSystemPrompt, errorMessages)
-
- // Verify the error is still thrown
- await expect(async () => {
- for await (const _chunk of stream) {
- // Should throw
- }
- }).rejects.toThrow()
-
- // Telemetry should have been captured before the error was thrown
- expect(mockCaptureException).toHaveBeenCalled()
- })
- })
})
// Additional tests for GPT-5 streaming event coverage
diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts
index e03abea6352..ed22288a27c 100644
--- a/src/api/providers/__tests__/openrouter.spec.ts
+++ b/src/api/providers/__tests__/openrouter.spec.ts
@@ -12,16 +12,6 @@ import { Package } from "../../../shared/package"
vitest.mock("openai")
vitest.mock("delay", () => ({ default: vitest.fn(() => Promise.resolve()) }))
-const mockCaptureException = vitest.fn()
-
-vitest.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: (...args: unknown[]) => mockCaptureException(...args),
- },
- },
-}))
-
vitest.mock("../fetchers/modelCache", () => ({
getModels: vitest.fn().mockImplementation(() => {
return Promise.resolve({
@@ -312,7 +302,7 @@ describe("OpenRouterHandler", () => {
)
})
- it("handles API errors and captures telemetry", async () => {
+ it("handles API errors", async () => {
const handler = new OpenRouterHandler(mockOptions)
const mockStream = {
async *[Symbol.asyncIterator]() {
@@ -327,20 +317,9 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("OpenRouter API Error 500: API Error")
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "API Error",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- errorCode: 500,
- status: 500,
- }),
- )
})
- it("captures telemetry when createMessage throws an exception", async () => {
+ it("propagates createMessage exceptions", async () => {
const handler = new OpenRouterHandler(mockOptions)
const mockCreate = vitest.fn().mockRejectedValue(new Error("Connection failed"))
;(OpenAI as any).prototype.chat = {
@@ -349,18 +328,9 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow()
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Connection failed",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- }),
- )
})
- it("passes SDK exceptions with status 429 to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions with status 429", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("Rate limit exceeded: free-models-per-day") as any
error.status = 429
@@ -372,18 +342,9 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("Rate limit exceeded")
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Rate limit exceeded: free-models-per-day",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- }),
- )
})
- it("passes SDK exceptions with 429 in message to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions with 429 in the message", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("429 Rate limit exceeded: free-models-per-day")
const mockCreate = vitest.fn().mockRejectedValue(error)
@@ -393,18 +354,9 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("429 Rate limit exceeded")
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "429 Rate limit exceeded: free-models-per-day",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- }),
- )
})
- it("passes SDK exceptions containing 'rate limit' to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions containing 'rate limit'", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("Request failed due to rate limit")
const mockCreate = vitest.fn().mockRejectedValue(error)
@@ -414,18 +366,9 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("rate limit")
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Request failed due to rate limit",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- }),
- )
})
- it("passes 429 rate limit errors from stream to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates 429 rate limit errors from stream", async () => {
const handler = new OpenRouterHandler(mockOptions)
const mockStream = {
async *[Symbol.asyncIterator]() {
@@ -440,17 +383,6 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("OpenRouter API Error 429: Rate limit exceeded")
-
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Rate limit exceeded",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "createMessage",
- errorCode: 429,
- status: 429,
- }),
- )
})
it("yields tool_call_end events when finish_reason is tool_calls", async () => {
@@ -553,7 +485,7 @@ describe("OpenRouterHandler", () => {
)
})
- it("handles API errors and captures telemetry", async () => {
+ it("handles API errors", async () => {
const handler = new OpenRouterHandler(mockOptions)
const mockError = {
error: {
@@ -568,21 +500,9 @@ describe("OpenRouterHandler", () => {
} as any
await expect(handler.completePrompt("test prompt")).rejects.toThrow("OpenRouter API Error 500: API Error")
-
- // Verify telemetry was captured
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "API Error",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- errorCode: 500,
- status: 500,
- }),
- )
})
- it("handles unexpected errors and captures telemetry", async () => {
+ it("handles unexpected errors", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("Unexpected error")
const mockCreate = vitest.fn().mockRejectedValue(error)
@@ -591,19 +511,9 @@ describe("OpenRouterHandler", () => {
} as any
await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error")
-
- // Verify telemetry was captured (filtering now happens inside PostHogTelemetryClient)
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Unexpected error",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- }),
- )
})
- it("passes SDK exceptions with status 429 to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions with status 429", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("Rate limit exceeded: free-models-per-day") as any
error.status = 429
@@ -613,19 +523,9 @@ describe("OpenRouterHandler", () => {
} as any
await expect(handler.completePrompt("test prompt")).rejects.toThrow("Rate limit exceeded")
-
- // captureException is called, but PostHogTelemetryClient filters out 429 errors internally
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Rate limit exceeded: free-models-per-day",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- }),
- )
})
- it("passes SDK exceptions with 429 in message to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions with 429 in the message", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("429 Rate limit exceeded: free-models-per-day")
const mockCreate = vitest.fn().mockRejectedValue(error)
@@ -634,19 +534,9 @@ describe("OpenRouterHandler", () => {
} as any
await expect(handler.completePrompt("test prompt")).rejects.toThrow("429 Rate limit exceeded")
-
- // captureException is called, but PostHogTelemetryClient filters out 429 errors internally
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "429 Rate limit exceeded: free-models-per-day",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- }),
- )
})
- it("passes SDK exceptions containing 'rate limit' to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates SDK exceptions containing 'rate limit'", async () => {
const handler = new OpenRouterHandler(mockOptions)
const error = new Error("Request failed due to rate limit")
const mockCreate = vitest.fn().mockRejectedValue(error)
@@ -655,19 +545,9 @@ describe("OpenRouterHandler", () => {
} as any
await expect(handler.completePrompt("test prompt")).rejects.toThrow("rate limit")
-
- // captureException is called, but PostHogTelemetryClient filters out rate limit errors internally
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Request failed due to rate limit",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- }),
- )
})
- it("passes 429 rate limit errors from response to telemetry (filtering happens in PostHogTelemetryClient)", async () => {
+ it("propagates 429 rate limit errors from response", async () => {
const handler = new OpenRouterHandler(mockOptions)
const mockError = {
error: {
@@ -684,18 +564,6 @@ describe("OpenRouterHandler", () => {
await expect(handler.completePrompt("test prompt")).rejects.toThrow(
"OpenRouter API Error 429: Rate limit exceeded",
)
-
- // captureException is called, but PostHogTelemetryClient filters out 429 errors internally
- expect(mockCaptureException).toHaveBeenCalledWith(
- expect.objectContaining({
- message: "Rate limit exceeded",
- provider: "OpenRouter",
- modelId: mockOptions.openRouterModelId,
- operation: "completePrompt",
- errorCode: 429,
- status: 429,
- }),
- )
})
})
})
diff --git a/src/api/providers/__tests__/xai.spec.ts b/src/api/providers/__tests__/xai.spec.ts
index 763d10d0277..6f5ddaea0bb 100644
--- a/src/api/providers/__tests__/xai.spec.ts
+++ b/src/api/providers/__tests__/xai.spec.ts
@@ -1,15 +1,5 @@
// npx vitest api/providers/__tests__/xai.spec.ts
-// Mock TelemetryService - must come before other imports
-const mockCaptureException = vitest.hoisted(() => vitest.fn())
-vitest.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureException: mockCaptureException,
- },
- },
-}))
-
const mockResponsesCreate = vitest.fn()
vitest.mock("openai", () => {
@@ -53,7 +43,6 @@ describe("XAIHandler", () => {
beforeEach(() => {
vi.clearAllMocks()
mockResponsesCreate.mockClear()
- mockCaptureException.mockClear()
handler = new XAIHandler({})
})
diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts
index 1786a105a5e..5ccf38500cc 100644
--- a/src/api/providers/anthropic.ts
+++ b/src/api/providers/anthropic.ts
@@ -9,9 +9,7 @@ import {
anthropicDefaultModelId,
anthropicModels,
ANTHROPIC_DEFAULT_MAX_TOKENS,
- ApiProviderError,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -112,96 +110,72 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
- try {
- stream = await this.client.messages.create(
- {
- model: modelId,
- max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
- temperature,
- thinking,
- // Setting cache breakpoint for system prompt so new tasks can reuse it.
- system: [{ text: systemPrompt, type: "text", cache_control: cacheControl }],
- messages: sanitizedMessages.map((message, index) => {
- if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
- return {
- ...message,
- content:
- typeof message.content === "string"
- ? [{ type: "text", text: message.content, cache_control: cacheControl }]
- : message.content.map((content, contentIndex) =>
- contentIndex === message.content.length - 1
- ? { ...content, cache_control: cacheControl }
- : content,
- ),
- }
- }
- return message
- }),
- stream: true,
- ...nativeToolParams,
- },
- (() => {
- // prompt caching: https://x.com/alexalbert__/status/1823751995901272068
- // https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
- // https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
-
- // Then check for models that support prompt caching
- switch (modelId) {
- case "claude-sonnet-4-6":
- case "claude-sonnet-4-5":
- case "claude-sonnet-4-20250514":
- case "claude-opus-4-6":
- case "claude-opus-4-5-20251101":
- case "claude-opus-4-1-20250805":
- case "claude-opus-4-20250514":
- case "claude-3-7-sonnet-20250219":
- case "claude-3-5-sonnet-20241022":
- case "claude-3-5-haiku-20241022":
- case "claude-3-opus-20240229":
- case "claude-haiku-4-5-20251001":
- case "claude-3-haiku-20240307":
- betas.push("prompt-caching-2024-07-31")
- return { headers: { "anthropic-beta": betas.join(",") } }
- default:
- return undefined
- }
- })(),
- )
- } catch (error) {
- TelemetryService.instance.captureException(
- new ApiProviderError(
- error instanceof Error ? error.message : String(error),
- this.providerName,
- modelId,
- "createMessage",
- ),
- )
- throw error
- }
- break
- }
- default: {
- try {
- stream = (await this.client.messages.create({
+ stream = await this.client.messages.create(
+ {
model: modelId,
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
temperature,
- system: [{ text: systemPrompt, type: "text" }],
- messages: sanitizedMessages,
+ thinking,
+ // Setting cache breakpoint for system prompt so new tasks can reuse it.
+ system: [{ text: systemPrompt, type: "text", cache_control: cacheControl }],
+ messages: sanitizedMessages.map((message, index) => {
+ if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
+ return {
+ ...message,
+ content:
+ typeof message.content === "string"
+ ? [{ type: "text", text: message.content, cache_control: cacheControl }]
+ : message.content.map((content, contentIndex) =>
+ contentIndex === message.content.length - 1
+ ? { ...content, cache_control: cacheControl }
+ : content,
+ ),
+ }
+ }
+ return message
+ }),
stream: true,
...nativeToolParams,
- })) as any
- } catch (error) {
- TelemetryService.instance.captureException(
- new ApiProviderError(
- error instanceof Error ? error.message : String(error),
- this.providerName,
- modelId,
- "createMessage",
- ),
- )
- throw error
- }
+ },
+ (() => {
+ // prompt caching: https://x.com/alexalbert__/status/1823751995901272068
+ // https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
+ // https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
+
+ // Then check for models that support prompt caching
+ switch (modelId) {
+ case "claude-sonnet-4-6":
+ case "claude-sonnet-4-5":
+ case "claude-sonnet-4-20250514":
+ case "claude-opus-4-6":
+ case "claude-opus-4-5-20251101":
+ case "claude-opus-4-1-20250805":
+ case "claude-opus-4-20250514":
+ case "claude-3-7-sonnet-20250219":
+ case "claude-3-5-sonnet-20241022":
+ case "claude-3-5-haiku-20241022":
+ case "claude-3-opus-20240229":
+ case "claude-haiku-4-5-20251001":
+ case "claude-3-haiku-20240307":
+ betas.push("prompt-caching-2024-07-31")
+ return { headers: { "anthropic-beta": betas.join(",") } }
+ default:
+ return undefined
+ }
+ })(),
+ )
+ break
+ }
+ default: {
+ stream = (await this.client.messages.create({
+ model: modelId,
+ max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
+ temperature,
+ system: [{ text: systemPrompt, type: "text" }],
+ messages: sanitizedMessages,
+ stream: true,
+ ...nativeToolParams,
+ })) as any
break
}
}
@@ -382,27 +356,14 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
async completePrompt(prompt: string) {
let { id: model, temperature } = this.getModel()
- let message
- try {
- message = await this.client.messages.create({
- model,
- max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS,
- thinking: undefined,
- temperature,
- messages: [{ role: "user", content: prompt }],
- stream: false,
- })
- } catch (error) {
- TelemetryService.instance.captureException(
- new ApiProviderError(
- error instanceof Error ? error.message : String(error),
- this.providerName,
- model,
- "completePrompt",
- ),
- )
- throw error
- }
+ const message = await this.client.messages.create({
+ model,
+ max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS,
+ thinking: undefined,
+ temperature,
+ messages: [{ role: "user", content: prompt }],
+ stream: false,
+ })
const content = message.content.find(({ type }) => type === "text")
return content?.type === "text" ? content.text : ""
diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts
index 3ceb2510033..2116dff08a7 100644
--- a/src/api/providers/bedrock.ts
+++ b/src/api/providers/bedrock.ts
@@ -30,9 +30,7 @@ import {
BEDROCK_GLOBAL_INFERENCE_MODEL_IDS,
BEDROCK_SERVICE_TIER_MODEL_IDS,
BEDROCK_SERVICE_TIER_PRICING,
- ApiProviderError,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { ApiStream } from "../transform/stream"
import { BaseProvider } from "./base-provider"
@@ -682,10 +680,8 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
// Clear timeout on error
clearTimeout(timeoutId)
- // Capture error in telemetry before processing
+ // Capture error message before processing
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, modelConfig.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
// Check if this is a throttling error that should trigger retry logic
const errorType = this.getErrorType(error)
@@ -790,11 +786,9 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
}
return ""
} catch (error) {
- // Capture error in telemetry
+ // Capture error message
const model = this.getModel()
- const telemetryErrorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(telemetryErrorMessage, this.providerName, model.id, "completePrompt")
- TelemetryService.instance.captureException(apiError)
+ const providerErrorMessage = error instanceof Error ? error.message : String(error)
// Use the extracted error handling method for all errors
const errorResult = this.handleBedrockError(error, false) // false for non-streaming context
diff --git a/src/api/providers/fetchers/__tests__/modelCache.spec.ts b/src/api/providers/fetchers/__tests__/modelCache.spec.ts
index 60a39fa15f7..19cbad23a5d 100644
--- a/src/api/providers/fetchers/__tests__/modelCache.spec.ts
+++ b/src/api/providers/fetchers/__tests__/modelCache.spec.ts
@@ -1,14 +1,5 @@
// Mocks must come first, before imports
-// Mock TelemetryService
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureEvent: vi.fn(),
- },
- },
-}))
-
// Mock NodeCache to allow controlling cache behavior
vi.mock("node-cache", () => {
const mockGet = vi.fn().mockReturnValue(undefined)
diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts
index a2c98e49caf..71423f44d3c 100644
--- a/src/api/providers/fetchers/modelCache.ts
+++ b/src/api/providers/fetchers/modelCache.ts
@@ -6,8 +6,7 @@ import NodeCache from "node-cache"
import { z } from "zod"
import type { ProviderName, ModelRecord } from "@roo-code/types"
-import { modelInfoSchema, TelemetryEventName } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
+import { modelInfoSchema } from "@roo-code/types"
import { safeWriteJson } from "../../../utils/safeWriteJson"
@@ -129,20 +128,14 @@ export const getModels = async (options: GetModelsOptions): Promise
models = await fetchModelsFromProvider(options)
const modelCount = Object.keys(models).length
- // Only cache non-empty results to prevent persisting failed API responses
- // Empty results could indicate API failure rather than "no models exist"
+ // Only cache non-empty results to prevent persisting failed API responses.
+ // Empty results could indicate API failure rather than "no models exist".
if (modelCount > 0) {
memoryCache.set(provider, models)
await writeModels(provider, models).catch((err) =>
console.error(`[MODEL_CACHE] Error writing ${provider} models to file cache:`, err),
)
- } else {
- TelemetryService.instance.captureEvent(TelemetryEventName.MODEL_CACHE_EMPTY_RESPONSE, {
- provider,
- context: "getModels",
- hasExistingCache: false,
- })
}
return models
@@ -186,17 +179,7 @@ export const refreshModels = async (options: GetModelsOptions): Promise 0,
- existingCacheSize: existingCount,
- })
- if (existingCount > 0) {
- return existingCache!
- } else {
- return {}
- }
+ return existingCount > 0 ? existingCache! : {}
}
// Update memory cache first
diff --git a/src/api/providers/gemini.ts b/src/api/providers/gemini.ts
index a49073ea334..7c4b49fd632 100644
--- a/src/api/providers/gemini.ts
+++ b/src/api/providers/gemini.ts
@@ -9,15 +9,8 @@ import {
} from "@google/genai"
import type { JWTInput } from "google-auth-library"
-import {
- type ModelInfo,
- type GeminiModelId,
- geminiDefaultModelId,
- geminiModels,
- ApiProviderError,
-} from "@roo-code/types"
+import { type ModelInfo, type GeminiModelId, geminiDefaultModelId, geminiModels } from "@roo-code/types"
import { safeJsonParse } from "@roo-code/core"
-import { TelemetryService } from "@roo-code/telemetry"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -335,8 +328,6 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model, "createMessage")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(t("common:errors.gemini.generate_stream", { error: error.message }))
@@ -444,8 +435,6 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
return text
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model, "completePrompt")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(t("common:errors.gemini.generate_complete_prompt", { error: error.message }))
diff --git a/src/api/providers/mistral.ts b/src/api/providers/mistral.ts
index e0e19298f42..e0a6f43558e 100644
--- a/src/api/providers/mistral.ts
+++ b/src/api/providers/mistral.ts
@@ -2,14 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
import { Mistral } from "@mistralai/mistralai"
import OpenAI from "openai"
-import {
- type MistralModelId,
- mistralDefaultModelId,
- mistralModels,
- MISTRAL_DEFAULT_TEMPERATURE,
- ApiProviderError,
-} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
+import { type MistralModelId, mistralDefaultModelId, mistralModels, MISTRAL_DEFAULT_TEMPERATURE } from "@roo-code/types"
import { ApiHandlerOptions } from "../../shared/api"
@@ -106,8 +99,6 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
response = await this.client.chat.stream(requestOptions)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model, "createMessage")
- TelemetryService.instance.captureException(apiError)
throw new Error(`Mistral completion error: ${errorMessage}`)
}
@@ -216,8 +207,6 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
return content || ""
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model, "completePrompt")
- TelemetryService.instance.captureException(apiError)
throw new Error(`Mistral completion error: ${errorMessage}`)
}
}
diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts
index 9dfb37bc72c..e0ae65f73bc 100644
--- a/src/api/providers/openai-codex.ts
+++ b/src/api/providers/openai-codex.ts
@@ -10,9 +10,7 @@ import {
openAiCodexModels,
type ReasoningEffort,
type ReasoningEffortExtended,
- ApiProviderError,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Package } from "../../shared/package"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -581,8 +579,6 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
yield* this.handleStreamResponse(response.body, model)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
if (error.message.includes("Codex API")) {
@@ -858,8 +854,6 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(t("common:errors.openAiCodex.streamProcessingError", { message: error.message }))
@@ -1246,8 +1240,6 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
} catch (error) {
const errorModel = this.getModel()
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, errorModel.id, "completePrompt")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(t("common:errors.openAiCodex.completionError", { message: error.message }))
diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts
index 6ce93827636..a04c17fe284 100644
--- a/src/api/providers/openai-native.ts
+++ b/src/api/providers/openai-native.ts
@@ -14,9 +14,7 @@ import {
type VerbosityLevel,
type ReasoningEffortExtended,
type ServiceTier,
- ApiProviderError,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -644,8 +642,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
} catch (error) {
const model = this.getModel()
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
// Re-throw with the original error message if it's already formatted
@@ -1126,8 +1122,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
// This can happen in certain edge cases and shouldn't break the flow
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(`Error processing response stream: ${error.message}`)
@@ -1571,8 +1565,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
} catch (error) {
const errorModel = this.getModel()
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, errorModel.id, "completePrompt")
- TelemetryService.instance.captureException(apiError)
if (error instanceof Error) {
throw new Error(`OpenAI Native completion error: ${error.message}`)
diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts
index 7fcc24b15f6..f154212eb09 100644
--- a/src/api/providers/openrouter.ts
+++ b/src/api/providers/openrouter.ts
@@ -4,14 +4,12 @@ import { z } from "zod"
import {
type ModelRecord,
- ApiProviderError,
openRouterDefaultModelId,
openRouterDefaultModelInfo,
OPENROUTER_DEFAULT_PROVIDER_NAME,
OPEN_ROUTER_PROMPT_CACHING_MODELS,
DEEP_SEEK_DEFAULT_TEMPERATURE,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
@@ -187,7 +185,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
}
/**
- * Handle OpenRouter streaming error response and report to telemetry.
+ * Handle OpenRouter streaming error response.
* OpenRouter may include metadata.raw with the actual upstream provider error.
* @param error The error object (not wrapped - receives the error directly)
*/
@@ -196,13 +194,6 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
const parsedError = extractErrorFromMetadataRaw(rawString)
const rawErrorMessage = parsedError || error?.message || "Unknown error"
- const apiError = Object.assign(
- new ApiProviderError(rawErrorMessage, this.providerName, modelId, operation, error?.code),
- { status: error?.code, error },
- )
-
- TelemetryService.instance.captureException(apiError)
-
throw new Error(`OpenRouter API Error ${error?.code}: ${rawErrorMessage}`)
}
@@ -342,36 +333,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
} catch (error) {
// Try to parse as OpenRouter error structure using Zod
const parseResult = OpenRouterErrorResponseSchema.safeParse(error)
-
- if (parseResult.success && parseResult.data.error) {
- const openRouterError = parseResult.data
- const rawString = openRouterError.error?.metadata?.raw
- const parsedError = extractErrorFromMetadataRaw(rawString)
- const rawErrorMessage = parsedError || openRouterError.error?.message || "Unknown error"
-
- const apiError = Object.assign(
- new ApiProviderError(
- rawErrorMessage,
- this.providerName,
- modelId,
- "createMessage",
- openRouterError.error?.code,
- ),
- {
- status: openRouterError.error?.code,
- error: openRouterError.error,
- },
- )
-
- TelemetryService.instance.captureException(apiError)
- throw handleOpenAIError(error, this.providerName)
- } else {
- // Fallback for non-OpenRouter errors
- const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "createMessage")
- TelemetryService.instance.captureException(apiError)
- throw handleOpenAIError(error, this.providerName)
- }
+ void parseResult
+ throw handleOpenAIError(error, this.providerName)
}
let lastUsage: CompletionUsage | undefined = undefined
@@ -607,36 +570,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
} catch (error) {
// Try to parse as OpenRouter error structure using Zod
const parseResult = OpenRouterErrorResponseSchema.safeParse(error)
-
- if (parseResult.success && parseResult.data.error) {
- const openRouterError = parseResult.data
- const rawString = openRouterError.error?.metadata?.raw
- const parsedError = extractErrorFromMetadataRaw(rawString)
- const rawErrorMessage = parsedError || openRouterError.error?.message || "Unknown error"
-
- const apiError = Object.assign(
- new ApiProviderError(
- rawErrorMessage,
- this.providerName,
- modelId,
- "completePrompt",
- openRouterError.error?.code,
- ),
- {
- status: openRouterError.error?.code,
- error: openRouterError.error,
- },
- )
-
- TelemetryService.instance.captureException(apiError)
- throw handleOpenAIError(error, this.providerName)
- } else {
- // Fallback for non-OpenRouter errors
- const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "completePrompt")
- TelemetryService.instance.captureException(apiError)
- throw handleOpenAIError(error, this.providerName)
- }
+ void parseResult
+ throw handleOpenAIError(error, this.providerName)
}
if ("error" in response) {
diff --git a/src/api/providers/poe.ts b/src/api/providers/poe.ts
index 536d222acd8..f75ea78bac8 100644
--- a/src/api/providers/poe.ts
+++ b/src/api/providers/poe.ts
@@ -8,9 +8,7 @@ import {
getPoeDefaultModelInfo,
type ModelInfo,
type ReasoningEffortExtended,
- ApiProviderError,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { shouldUseReasoningBudget, shouldUseReasoningEffort, type ApiHandlerOptions } from "../../shared/api"
@@ -104,7 +102,6 @@ export class PoeHandler extends BaseProvider implements SingleCompletionHandler
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
throw new Error(`Poe completion error: ${errorMessage}`)
}
@@ -129,7 +126,6 @@ export class PoeHandler extends BaseProvider implements SingleCompletionHandler
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
throw new Error(`Poe streaming error: ${errorMessage}`)
}
}
@@ -144,7 +140,6 @@ export class PoeHandler extends BaseProvider implements SingleCompletionHandler
return text
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "completePrompt"))
throw new Error(`Poe completion error: ${errorMessage}`)
}
}
diff --git a/src/api/providers/utils/error-handler.ts b/src/api/providers/utils/error-handler.ts
index 2c55b96f9cf..720bdde52d3 100644
--- a/src/api/providers/utils/error-handler.ts
+++ b/src/api/providers/utils/error-handler.ts
@@ -6,7 +6,7 @@
* - Preserves HTTP status codes for UI-aware error display
* - Maintains error details for retry logic (e.g., RetryInfo for 429 errors)
* - Provides consistent error message formatting
- * - Enables telemetry and debugging with complete error context
+ * - Enables debugging with complete error context
*/
import i18n from "../../../i18n/setup"
diff --git a/src/api/providers/xai.ts b/src/api/providers/xai.ts
index 0cd9cb0273b..4db605c1f3e 100644
--- a/src/api/providers/xai.ts
+++ b/src/api/providers/xai.ts
@@ -1,8 +1,7 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
-import { type XAIModelId, xaiDefaultModelId, xaiModels, ApiProviderError } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
+import { type XAIModelId, xaiDefaultModelId, xaiModels } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
@@ -132,8 +131,6 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
} as any)) as unknown as AsyncIterable
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
- TelemetryService.instance.captureException(apiError)
throw handleOpenAIError(error, this.providerName)
}
@@ -155,8 +152,6 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
return response.output_text || ""
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
- const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "completePrompt")
- TelemetryService.instance.captureException(apiError)
throw handleOpenAIError(error, this.providerName)
}
}
diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts
index bda7c71eb8d..c391f9852cf 100644
--- a/src/core/assistant-message/NativeToolCallParser.ts
+++ b/src/core/assistant-message/NativeToolCallParser.ts
@@ -391,7 +391,7 @@ export class NativeToolCallParser {
// Build partial nativeArgs based on what we have so far
let nativeArgs: any = undefined
- // Track if legacy format was used (for telemetry)
+ // Track whether legacy format was used
let usedLegacyFormat = false
switch (name) {
@@ -654,7 +654,7 @@ export class NativeToolCallParser {
result.originalName = originalName
}
- // Track legacy format usage for telemetry
+ // Track legacy format usage
if (usedLegacyFormat) {
result.usedLegacyFormat = true
}
@@ -722,7 +722,7 @@ export class NativeToolCallParser {
// nativeArgs object. If validation fails, we treat the tool call as invalid and fail fast.
let nativeArgs: NativeArgsFor | undefined = undefined
- // Track if legacy format was used (for telemetry)
+ // Track whether legacy format was used
let usedLegacyFormat = false
switch (resolvedName) {
@@ -1017,7 +1017,7 @@ export class NativeToolCallParser {
result.originalName = toolCall.name
}
- // Track legacy format usage for telemetry
+ // Track legacy format usage
if (usedLegacyFormat) {
result.usedLegacyFormat = true
}
diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts
index 6675f18ce82..78385538351 100644
--- a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts
+++ b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts
@@ -23,16 +23,6 @@ vi.mock("@roo-code/core", () => ({
},
}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureToolUsage: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- },
- },
-}))
-
-import { TelemetryService } from "@roo-code/telemetry"
import { customToolRegistry } from "@roo-code/core"
describe("presentAssistantMessage - Custom Tool Recording", () => {
@@ -118,7 +108,6 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
// Should record as "custom_tool", not "my_custom_tool"
expect(mockTask.recordToolUsage).toHaveBeenCalledWith("custom_tool")
- expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "custom_tool")
})
})
@@ -171,7 +160,6 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
// Should record as "read_file", not "custom_tool"
expect(mockTask.recordToolUsage).toHaveBeenCalledWith("read_file")
- expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "read_file")
})
it("should record MCP tool usage as 'use_mcp_tool' (not custom_tool)", async () => {
@@ -213,7 +201,6 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
// Should record as "use_mcp_tool", not "custom_tool"
expect(mockTask.recordToolUsage).toHaveBeenCalledWith("use_mcp_tool")
- expect(TelemetryService.instance.captureToolUsage).toHaveBeenCalledWith(mockTask.taskId, "use_mcp_tool")
})
})
@@ -355,7 +342,6 @@ describe("presentAssistantMessage - Custom Tool Recording", () => {
// Should not record usage for partial blocks
expect(mockTask.recordToolUsage).not.toHaveBeenCalled()
- expect(TelemetryService.instance.captureToolUsage).not.toHaveBeenCalled()
})
})
})
diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts
index fcf778b8f81..bede61c281b 100644
--- a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts
+++ b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts
@@ -6,23 +6,6 @@ import { presentAssistantMessage } from "../presentAssistantMessage"
import { Task } from "../../task/Task"
// Mock dependencies
-vi.mock("../../task/Task")
-vi.mock("../../tools/validateToolUse", () => ({
- validateToolUse: vi.fn(),
- isValidToolName: vi.fn((toolName: string) =>
- ["read_file", "write_to_file", "ask_followup_question", "attempt_completion", "use_mcp_tool"].includes(
- toolName,
- ),
- ),
-}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureToolUsage: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- },
- },
-}))
describe("presentAssistantMessage - Image Handling in Native Tool Calling", () => {
let mockTask: any
diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts
index 8e6c8d9d9e7..135c290eee0 100644
--- a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts
+++ b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts
@@ -9,14 +9,6 @@ vi.mock("../../tools/validateToolUse", () => ({
validateToolUse: vi.fn(),
isValidToolName: vi.fn(() => false),
}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureToolUsage: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- },
- },
-}))
describe("presentAssistantMessage - Unknown Tool Handling", () => {
let mockTask: any
diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts
index 7f5862be154..49ce56a305d 100644
--- a/src/core/assistant-message/presentAssistantMessage.ts
+++ b/src/core/assistant-message/presentAssistantMessage.ts
@@ -2,8 +2,6 @@ import { serializeError } from "serialize-error"
import { Anthropic } from "@anthropic-ai/sdk"
import type { ToolName, ClineAsk, ToolProgressStatus } from "@roo-code/types"
-import { ConsecutiveMistakeError, TelemetryEventName } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { customToolRegistry } from "@roo-code/core"
import { t } from "../../i18n"
@@ -234,8 +232,7 @@ export async function presentAssistantMessage(cline: Task) {
}
if (!mcpBlock.partial) {
- cline.recordToolUsage("use_mcp_tool") // Record as use_mcp_tool for analytics
- TelemetryService.instance.captureToolUsage(cline.taskId, "use_mcp_tool")
+ cline.recordToolUsage("use_mcp_tool")
}
// Resolve sanitized server name back to original server name
@@ -302,7 +299,7 @@ export async function presentAssistantMessage(cline: Task) {
if (!toolCallId) {
const errorMessage =
"Invalid tool call: missing tool_use.id. XML tool calls are no longer supported. Remove any XML tool markup (e.g. ...) and use native tool calling instead."
- // Record a tool error for visibility/telemetry. Use the reported tool name if present.
+ // Record a tool error for visibility. Use the reported tool name if present.
try {
if (
typeof (cline as any).recordToolError === "function" &&
@@ -558,16 +555,6 @@ export async function presentAssistantMessage(cline: Task) {
const isCustomTool = stateExperiments?.customTools && customToolRegistry.has(block.name)
const recordName = isCustomTool ? "custom_tool" : block.name
cline.recordToolUsage(recordName)
- TelemetryService.instance.captureToolUsage(cline.taskId, recordName)
-
- // Track legacy format usage for read_file tool (for migration monitoring)
- if (block.name === "read_file" && block.usedLegacyFormat) {
- const modelInfo = cline.api.getModel()
- TelemetryService.instance.captureEvent(TelemetryEventName.READ_FILE_LEGACY_FORMAT_USED, {
- taskId: cline.taskId,
- model: modelInfo?.id,
- })
- }
}
// Validate tool use before execution - ONLY for complete (non-partial) blocks.
@@ -651,20 +638,6 @@ export async function presentAssistantMessage(cline: Task) {
await cline.say("user_feedback", text, images)
}
- // Track tool repetition in telemetry via PostHog exception tracking and event.
- TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId)
- TelemetryService.instance.captureException(
- new ConsecutiveMistakeError(
- `Tool repetition limit reached for ${block.name}`,
- cline.taskId,
- cline.consecutiveMistakeCount,
- cline.consecutiveMistakeLimit,
- "tool_repetition",
- cline.apiConfiguration.apiProvider,
- cline.api.getModel().id,
- ),
- )
-
// Return tool result message about the repetition
pushToolResult(
formatResponse.toolError(
diff --git a/src/core/checkpoints/__tests__/checkpoint.test.ts b/src/core/checkpoints/__tests__/checkpoint.test.ts
index 299ff823b87..dd6bd60a2dc 100644
--- a/src/core/checkpoints/__tests__/checkpoint.test.ts
+++ b/src/core/checkpoints/__tests__/checkpoint.test.ts
@@ -22,15 +22,6 @@ vi.mock("vscode", () => ({
}))
// Mock other dependencies
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureCheckpointCreated: vi.fn(),
- captureCheckpointRestored: vi.fn(),
- captureCheckpointDiffed: vi.fn(),
- },
- },
-}))
vi.mock("../../../utils/path", () => ({
getWorkspacePath: vi.fn(() => "/test/workspace"),
diff --git a/src/core/checkpoints/index.ts b/src/core/checkpoints/index.ts
index 26a137b939c..9bf6dd4dfb4 100644
--- a/src/core/checkpoints/index.ts
+++ b/src/core/checkpoints/index.ts
@@ -2,7 +2,6 @@ import pWaitFor from "p-wait-for"
import * as vscode from "vscode"
import type { ClineApiReqInfo } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../task/Task"
@@ -216,8 +215,6 @@ export async function checkpointSave(task: Task, force = false, suppressMessage
return
}
- TelemetryService.instance.captureCheckpointCreated(task.taskId)
-
// Start the checkpoint process in the background.
return service
.saveCheckpoint(`Task: ${task.taskId}, Time: ${Date.now()}`, { allowEmpty: force, suppressMessage })
@@ -254,7 +251,6 @@ export async function checkpointRestore(
try {
await service.restoreCheckpoint(commitHash)
- TelemetryService.instance.captureCheckpointRestored(task.taskId)
await provider?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash })
if (mode === "restore") {
@@ -321,8 +317,6 @@ export async function checkpointDiff(task: Task, { ts, previousCommitHash, commi
return
}
- TelemetryService.instance.captureCheckpointDiffed(task.taskId)
-
let fromHash: string | undefined
let toHash: string | undefined
let title: string
diff --git a/src/core/condense/__tests__/condense.spec.ts b/src/core/condense/__tests__/condense.spec.ts
index c209fa97243..533499f1641 100644
--- a/src/core/condense/__tests__/condense.spec.ts
+++ b/src/core/condense/__tests__/condense.spec.ts
@@ -2,7 +2,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import type { ModelInfo } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { BaseProvider } from "../../../api/providers/base-provider"
import { ApiMessage } from "../../task-persistence/apiMessages"
@@ -57,11 +56,7 @@ const mockApiHandler = new MockApiHandler()
const taskId = "test-task-id"
describe("Condense", () => {
- beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
- })
+ beforeEach(() => {})
describe("extractCommandBlocks", () => {
it("should extract command blocks from string content", () => {
diff --git a/src/core/condense/__tests__/foldedFileContext.spec.ts b/src/core/condense/__tests__/foldedFileContext.spec.ts
index 3bd9b390f5a..05c5232f134 100644
--- a/src/core/condense/__tests__/foldedFileContext.spec.ts
+++ b/src/core/condense/__tests__/foldedFileContext.spec.ts
@@ -3,7 +3,6 @@
import * as path from "path"
import { Anthropic } from "@anthropic-ai/sdk"
import type { ModelInfo } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { BaseProvider } from "../../../api/providers/base-provider"
// Mock the tree-sitter module
@@ -213,11 +212,7 @@ describe("foldedFileContext", () => {
})
describe("summarizeConversation with foldedFileContext", () => {
- beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
- })
+ beforeEach(() => {})
// Mock API handler for testing
class MockApiHandler extends BaseProvider {
diff --git a/src/core/condense/__tests__/index.spec.ts b/src/core/condense/__tests__/index.spec.ts
index 10092f71dc7..5dea4af64d9 100644
--- a/src/core/condense/__tests__/index.spec.ts
+++ b/src/core/condense/__tests__/index.spec.ts
@@ -3,7 +3,6 @@
import type { Mock } from "vitest"
import { Anthropic } from "@anthropic-ai/sdk"
-import { TelemetryService } from "@roo-code/telemetry"
import { ApiHandler } from "../../../api"
import { ApiMessage } from "../../task-persistence/apiMessages"
@@ -25,14 +24,6 @@ vi.mock("../../../api/transform/image-cleaning", () => ({
maybeRemoveImageBlocks: vi.fn((messages: ApiMessage[], _apiHandler: ApiHandler) => [...messages]),
}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureContextCondensed: vi.fn(),
- },
- },
-}))
-
const taskId = "test-task-id"
describe("extractCommandBlocks", () => {
@@ -1150,9 +1141,6 @@ describe("summarizeConversation with custom settings", () => {
// Reset mocks
vi.clearAllMocks()
- // Reset telemetry mock
- ;(TelemetryService.instance.captureContextCondensed as Mock).mockClear()
-
// Setup mock API handler
mockMainApiHandler = {
createMessage: vi.fn().mockImplementation(() => {
@@ -1243,48 +1231,6 @@ describe("summarizeConversation with custom settings", () => {
)
expect(createMessageCalls[0][0]).toContain("CRITICAL: This is a summarization-only request")
})
-
- /**
- * Test that telemetry is called for custom prompt usage
- */
- it("should capture telemetry when using custom prompt", async () => {
- await summarizeConversation({
- messages: sampleMessages,
- apiHandler: mockMainApiHandler,
- systemPrompt: defaultSystemPrompt,
- taskId: localTaskId,
- isAutomaticTrigger: false,
- customCondensingPrompt: "Custom prompt",
- })
-
- // Verify telemetry was called with custom prompt flag
- expect(TelemetryService.instance.captureContextCondensed).toHaveBeenCalledWith(
- localTaskId,
- false,
- true, // usedCustomPrompt
- )
- })
-
- /**
- * Test that telemetry is called with isAutomaticTrigger flag
- */
- it("should capture telemetry with isAutomaticTrigger flag", async () => {
- await summarizeConversation({
- messages: sampleMessages,
- apiHandler: mockMainApiHandler,
- systemPrompt: defaultSystemPrompt,
- taskId: localTaskId,
- isAutomaticTrigger: true,
- customCondensingPrompt: "Custom prompt",
- })
-
- // Verify telemetry was called with isAutomaticTrigger flag
- expect(TelemetryService.instance.captureContextCondensed).toHaveBeenCalledWith(
- localTaskId,
- true, // isAutomaticTrigger
- true, // usedCustomPrompt
- )
- })
})
describe("toolUseToText", () => {
diff --git a/src/core/condense/__tests__/rewind-after-condense.spec.ts b/src/core/condense/__tests__/rewind-after-condense.spec.ts
index 586e7f5f88f..faae00b2be4 100644
--- a/src/core/condense/__tests__/rewind-after-condense.spec.ts
+++ b/src/core/condense/__tests__/rewind-after-condense.spec.ts
@@ -9,17 +9,11 @@
* so they can be sent to the API again.
*/
-import { TelemetryService } from "@roo-code/telemetry"
-
import { getEffectiveApiHistory, cleanupAfterTruncation } from "../index"
import { ApiMessage } from "../../task-persistence/apiMessages"
describe("Rewind After Condense - Issue #8295", () => {
- beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
- })
+ beforeEach(() => {})
describe("getEffectiveApiHistory", () => {
it("should return summary and messages after summary (fresh start model)", () => {
diff --git a/src/core/condense/index.ts b/src/core/condense/index.ts
index 0438bf6bcb1..6245b5031a5 100644
--- a/src/core/condense/index.ts
+++ b/src/core/condense/index.ts
@@ -1,8 +1,6 @@
import Anthropic from "@anthropic-ai/sdk"
import crypto from "crypto"
-import { TelemetryService } from "@roo-code/telemetry"
-
import { t } from "../../i18n"
import { ApiHandler, ApiHandlerCreateMessageMetadata } from "../../api"
import { ApiMessage } from "../task-persistence/apiMessages"
@@ -267,11 +265,6 @@ export async function summarizeConversation(options: SummarizeConversationOption
cwd,
rooIgnoreController,
} = options
- TelemetryService.instance.captureContextCondensed(
- taskId,
- isAutomaticTrigger ?? false,
- !!customCondensingPrompt?.trim(),
- )
const response: SummarizeResponse = { messages, cost: 0, summary: "" }
diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts
index 2825d1c9452..22bbd4484e4 100644
--- a/src/core/config/ContextProxy.ts
+++ b/src/core/config/ContextProxy.ts
@@ -18,7 +18,6 @@ import {
isProviderName,
isRetiredProvider,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { logger } from "../../utils/logging"
import { supportPrompt } from "../../shared/support-prompt"
@@ -406,10 +405,6 @@ export class ContextProxy {
try {
return globalSettingsSchema.parse(values)
} catch (error) {
- if (error instanceof ZodError) {
- TelemetryService.instance.captureSchemaValidationError({ schemaName: "GlobalSettings", error })
- }
-
return GLOBAL_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as GlobalSettings)
}
}
@@ -424,16 +419,12 @@ export class ContextProxy {
// Sanitize invalid/removed apiProvider values before parsing
// This handles cases where a user had a provider selected that was later removed
// from the extension (e.g., "glama"). We sanitize here to avoid repeated
- // schema validation errors that can cause infinite loops in telemetry.
+ // schema validation errors that can cause infinite update loops.
const sanitizedValues = this.sanitizeProviderValues(values)
try {
return providerSettingsSchema.parse(sanitizedValues)
} catch (error) {
- if (error instanceof ZodError) {
- TelemetryService.instance.captureSchemaValidationError({ schemaName: "ProviderSettings", error })
- }
-
return PROVIDER_SETTINGS_KEYS.reduce(
(acc, key) => ({ ...acc, [key]: sanitizedValues[key] }),
{} as ProviderSettings,
@@ -538,10 +529,6 @@ export class ContextProxy {
return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined))
} catch (error) {
- if (error instanceof ZodError) {
- TelemetryService.instance.captureSchemaValidationError({ schemaName: "GlobalSettings", error })
- }
-
return undefined
}
}
diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts
index 6088bd68fe2..66040100b8d 100644
--- a/src/core/config/ProviderSettingsManager.ts
+++ b/src/core/config/ProviderSettingsManager.ts
@@ -14,7 +14,6 @@ import {
isProviderName,
isRetiredProvider,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Mode, modes } from "../../shared/modes"
import { buildApiHandler } from "../../api"
@@ -623,13 +622,6 @@ export class ProviderSettingsManager {
),
}
} catch (error) {
- if (error instanceof ZodError) {
- TelemetryService.instance.captureSchemaValidationError({
- schemaName: "ProviderProfiles",
- error,
- })
- }
-
throw new Error(`Failed to read provider profiles from secrets: ${error}`)
}
}
diff --git a/src/core/config/__tests__/importExport.spec.ts b/src/core/config/__tests__/importExport.spec.ts
index 9873ffde945..88cbfa57bc0 100644
--- a/src/core/config/__tests__/importExport.spec.ts
+++ b/src/core/config/__tests__/importExport.spec.ts
@@ -6,7 +6,6 @@ import * as path from "path"
import * as vscode from "vscode"
import type { ProviderName } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { importSettings, importSettingsFromFile, importSettingsWithFeedback, exportSettings } from "../importExport"
import { ProviderSettingsManager } from "../ProviderSettingsManager"
@@ -103,10 +102,6 @@ describe("importExport", () => {
beforeEach(() => {
vi.clearAllMocks()
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
mockProviderSettingsManager = {
export: vi.fn(),
import: vi.fn(),
diff --git a/src/core/config/importExport.ts b/src/core/config/importExport.ts
index 542f5b07430..5de410bd1c9 100644
--- a/src/core/config/importExport.ts
+++ b/src/core/config/importExport.ts
@@ -12,7 +12,6 @@ import {
isProviderName,
type ProviderSettingsWithId,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager"
import { ContextProxy } from "./ContextProxy"
@@ -188,7 +187,6 @@ export async function importSettingsFromPath(
if (e instanceof ZodError) {
error = e.issues.map((issue) => `[${issue.path.join(".")}]: ${issue.message}`).join("\n")
- TelemetryService.instance.captureSchemaValidationError({ schemaName: "ImportExport", error: e })
} else if (e instanceof Error) {
error = e.message
}
diff --git a/src/core/context-management/__tests__/context-management.spec.ts b/src/core/context-management/__tests__/context-management.spec.ts
index 9950ec536b3..240269dd2c5 100644
--- a/src/core/context-management/__tests__/context-management.spec.ts
+++ b/src/core/context-management/__tests__/context-management.spec.ts
@@ -3,7 +3,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import type { ModelInfo } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { BaseProvider } from "../../../api/providers/base-provider"
import { ApiMessage } from "../../task-persistence/apiMessages"
@@ -51,11 +50,7 @@ const mockApiHandler = new MockApiHandler()
const taskId = "test-task-id"
describe("Context Management", () => {
- beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
- })
+ beforeEach(() => {})
/**
* Tests for the truncateConversation function
*/
diff --git a/src/core/context-management/__tests__/truncation.spec.ts b/src/core/context-management/__tests__/truncation.spec.ts
index 2e6cbed5b6e..ec71887e1d2 100644
--- a/src/core/context-management/__tests__/truncation.spec.ts
+++ b/src/core/context-management/__tests__/truncation.spec.ts
@@ -1,5 +1,4 @@
import { describe, it, expect, beforeEach } from "vitest"
-import { TelemetryService } from "@roo-code/telemetry"
import { truncateConversation } from "../index"
import { getEffectiveApiHistory, cleanupAfterTruncation } from "../../condense"
import { ApiMessage } from "../../task-persistence/apiMessages"
@@ -8,11 +7,6 @@ describe("Non-Destructive Sliding Window Truncation", () => {
let messages: ApiMessage[]
beforeEach(() => {
- // Initialize TelemetryService for tests
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
// Create a sample conversation with 11 messages (1 initial + 10 conversation messages)
messages = [
{ role: "user", content: "Initial task", ts: 1000 },
diff --git a/src/core/context-management/index.ts b/src/core/context-management/index.ts
index 243d7bd797f..4d9608d8e44 100644
--- a/src/core/context-management/index.ts
+++ b/src/core/context-management/index.ts
@@ -1,8 +1,6 @@
import { Anthropic } from "@anthropic-ai/sdk"
import crypto from "crypto"
-import { TelemetryService } from "@roo-code/telemetry"
-
import { ApiHandler, ApiHandlerCreateMessageMetadata } from "../../api"
import { MAX_CONDENSE_THRESHOLD, MIN_CONDENSE_THRESHOLD, summarizeConversation, SummarizeResponse } from "../condense"
import { ApiMessage } from "../task-persistence/apiMessages"
@@ -61,12 +59,10 @@ export type TruncationResult = {
*
* @param {ApiMessage[]} messages - The conversation messages.
* @param {number} fracToRemove - The fraction (between 0 and 1) of messages (excluding the first) to hide.
- * @param {string} taskId - The task ID for the conversation, used for telemetry
+ * @param {string} taskId - The task ID for the conversation
* @returns {TruncationResult} Object containing the tagged messages, truncation ID, and count of messages removed.
*/
export function truncateConversation(messages: ApiMessage[], fracToRemove: number, taskId: string): TruncationResult {
- TelemetryService.instance.captureSlidingWindowTruncation(taskId)
-
const truncationId = crypto.randomUUID()
// Filter to only visible messages (those not already truncated)
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index 005bb0f292b..97f07fcc7aa 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -36,7 +36,6 @@ import {
type ClineApiReqCancelReason,
type ClineApiReqInfo,
RooCodeEventName,
- TelemetryEventName,
TaskStatus,
TodoItem,
getApiProtocol,
@@ -50,12 +49,9 @@ import {
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
MAX_CHECKPOINT_TIMEOUT_SECONDS,
MIN_CHECKPOINT_TIMEOUT_SECONDS,
- ConsecutiveMistakeError,
MAX_MCP_TOOLS_THRESHOLD,
countEnabledMcpTools,
} from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
-import { CloudService } from "@roo-code/cloud"
// api
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
@@ -409,8 +405,6 @@ export class Task extends EventEmitter implements TaskLike {
private debouncedEmitTokenUsage: ReturnType
// Cloud Sync Tracking
- private cloudSyncedMessageTimestamps: Set = new Set()
-
// Initial status for the task's history item (set at creation time to avoid race conditions)
private readonly initialStatus?: "active" | "delegated" | "completed"
@@ -498,24 +492,6 @@ export class Task extends EventEmitter implements TaskLike {
this.taskNumber = taskNumber
this.initialStatus = initialStatus
- // Store the task's mode and API config name when it's created.
- // For history items, use the stored values; for new tasks, we'll set them
- // after getting state.
- if (historyItem) {
- this._taskMode = historyItem.mode || defaultModeSlug
- this._taskApiConfigName = historyItem.apiConfigName
- this.taskModeReady = Promise.resolve()
- this.taskApiConfigReady = Promise.resolve()
- TelemetryService.instance.captureTaskRestarted(this.taskId)
- } else {
- // For new tasks, don't set the mode/apiConfigName yet - wait for async initialization.
- this._taskMode = undefined
- this._taskApiConfigName = undefined
- this.taskModeReady = this.initializeTaskMode(provider)
- this.taskApiConfigReady = this.initializeTaskApiConfigName(provider)
- TelemetryService.instance.captureTaskCreated(this.taskId)
- }
-
this.assistantMessageParser = undefined
this.messageQueueService = new MessageQueueService()
@@ -563,6 +539,18 @@ export class Task extends EventEmitter implements TaskLike {
{ leading: true, trailing: true, maxWait: this.TOKEN_USAGE_EMIT_INTERVAL_MS },
)
+ if (historyItem) {
+ this._taskMode = historyItem.mode || defaultModeSlug
+ this._taskApiConfigName = historyItem.apiConfigName
+ this.taskModeReady = Promise.resolve()
+ this.taskApiConfigReady = Promise.resolve()
+ } else {
+ this._taskMode = undefined
+ this._taskApiConfigName = undefined
+ this.taskModeReady = this.initializeTaskMode(provider)
+ this.taskApiConfigReady = this.initializeTaskApiConfigName(provider)
+ }
+
onCreated?.(this)
if (startTask) {
@@ -1161,51 +1149,18 @@ export class Task extends EventEmitter implements TaskLike {
await provider?.postStateToWebviewWithoutTaskHistory()
this.emit(RooCodeEventName.Message, { action: "created", message })
await this.saveClineMessages()
-
- const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
-
- if (shouldCaptureMessage) {
- CloudService.instance.captureEvent({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: { taskId: this.taskId, message },
- })
- // Track that this message has been synced to cloud
- this.cloudSyncedMessageTimestamps.add(message.ts)
- }
}
public async overwriteClineMessages(newMessages: ClineMessage[]) {
this.clineMessages = newMessages
restoreTodoListForTask(this)
await this.saveClineMessages()
-
- // When overwriting messages (e.g., during task resume), repopulate the cloud sync tracking Set
- // with timestamps from all non-partial messages to prevent re-syncing previously synced messages
- this.cloudSyncedMessageTimestamps.clear()
- for (const msg of newMessages) {
- if (msg.partial !== true) {
- this.cloudSyncedMessageTimestamps.add(msg.ts)
- }
- }
}
private async updateClineMessage(message: ClineMessage) {
const provider = this.providerRef.deref()
await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message })
this.emit(RooCodeEventName.Message, { action: "updated", message })
-
- // Check if we should sync to cloud and haven't already synced this message
- const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
- const hasNotBeenSynced = !this.cloudSyncedMessageTimestamps.has(message.ts)
-
- if (shouldCaptureMessage && hasNotBeenSynced) {
- CloudService.instance.captureEvent({
- event: TelemetryEventName.TASK_MESSAGE,
- properties: { taskId: this.taskId, message },
- })
- // Track that this message has been synced to cloud
- this.cloudSyncedMessageTimestamps.add(message.ts)
- }
}
private async saveClineMessages(): Promise {
@@ -2526,22 +2481,6 @@ export class Task extends EventEmitter implements TaskLike {
}
if (this.consecutiveMistakeLimit > 0 && this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
- // Track consecutive mistake errors in telemetry via event and PostHog exception tracking.
- // The reason is "no_tools_used" because this limit is reached via initiateTaskLoop
- // which increments consecutiveMistakeCount when the model doesn't use any tools.
- TelemetryService.instance.captureConsecutiveMistakeError(this.taskId)
- TelemetryService.instance.captureException(
- new ConsecutiveMistakeError(
- `Task reached consecutive mistake limit (${this.consecutiveMistakeLimit})`,
- this.taskId,
- this.consecutiveMistakeCount,
- this.consecutiveMistakeLimit,
- "no_tools_used",
- this.apiConfiguration.apiProvider,
- getModelId(this.apiConfiguration),
- ),
- )
-
const { response, text, images } = await this.ask(
"mistake_limit_reached",
t("common:errors.mistake_limit_guidance"),
@@ -2657,7 +2596,6 @@ export class Task extends EventEmitter implements TaskLike {
((currentItem.retryAttempt ?? 0) === 0 && !isEmptyUserContent) || currentItem.userMessageWasRemoved
if (shouldAddUserMessage) {
await this.addToApiConversationHistory({ role: "user", content: finalUserContent })
- TelemetryService.instance.captureConversationMessage(this.taskId, "user")
}
// Since we sent off a placeholder api_req_started message to update the
@@ -3088,7 +3026,7 @@ export class Task extends EventEmitter implements TaskLike {
let bgCacheReadTokens = currentTokens.cacheRead
let bgTotalCost = currentTokens.total
- // Helper function to capture telemetry and update messages
+ // Helper function to update messages
const captureUsageData = async (
tokens: {
input: number
@@ -3105,56 +3043,19 @@ export class Task extends EventEmitter implements TaskLike {
tokens.cacheWrite > 0 ||
tokens.cacheRead > 0
) {
- // Update the shared variables atomically
inputTokens = tokens.input
outputTokens = tokens.output
cacheWriteTokens = tokens.cacheWrite
cacheReadTokens = tokens.cacheRead
totalCost = tokens.total
- // Update the API request message with the latest usage data
updateApiReqMsg()
await this.saveClineMessages()
- // Update the specific message in the webview
const apiReqMessage = this.clineMessages[messageIndex]
if (apiReqMessage) {
await this.updateClineMessage(apiReqMessage)
}
-
- // Capture telemetry with provider-aware cost calculation
- const modelId = getModelId(this.apiConfiguration)
- const apiProvider = this.apiConfiguration.apiProvider
- const apiProtocol = getApiProtocol(
- apiProvider && !isRetiredProvider(apiProvider) ? apiProvider : undefined,
- modelId,
- )
-
- // Use the appropriate cost function based on the API protocol
- const costResult =
- apiProtocol === "anthropic"
- ? calculateApiCostAnthropic(
- streamModelInfo,
- tokens.input,
- tokens.output,
- tokens.cacheWrite,
- tokens.cacheRead,
- )
- : calculateApiCostOpenAI(
- streamModelInfo,
- tokens.input,
- tokens.output,
- tokens.cacheWrite,
- tokens.cacheRead,
- )
-
- TelemetryService.instance.captureLlmCompletion(this.taskId, {
- inputTokens: costResult.totalInputTokens,
- outputTokens: costResult.totalOutputTokens,
- cacheWriteTokens: tokens.cacheWrite,
- cacheReadTokens: tokens.cacheRead,
- cost: tokens.total ?? costResult.totalCost,
- })
}
}
@@ -3468,8 +3369,8 @@ export class Task extends EventEmitter implements TaskLike {
assistantContent.push({
type: "tool_use" as const,
id: sanitizedId,
- name: mcpBlock.name, // Original dynamic name
- input: mcpBlock.arguments, // Direct tool arguments
+ name: mcpBlock.name,
+ input: mcpBlock.arguments,
})
}
} else {
@@ -3486,7 +3387,6 @@ export class Task extends EventEmitter implements TaskLike {
continue
}
seenToolUseIds.add(sanitizedId)
- // nativeArgs is already in the correct API format for all tools
const input = toolUse.nativeArgs || toolUse.params
// Use originalName (alias) if present for API history consistency.
@@ -3513,14 +3413,9 @@ export class Task extends EventEmitter implements TaskLike {
)
if (newTaskIndex !== -1 && newTaskIndex < assistantContent.length - 1) {
- // new_task found but not last - truncate subsequent tools
const truncatedTools = assistantContent.slice(newTaskIndex + 1)
- assistantContent.length = newTaskIndex + 1 // Truncate API history array
+ assistantContent.length = newTaskIndex + 1
- // ALSO truncate the execution array (assistantMessageContent) to prevent
- // tools after new_task from being executed by presentAssistantMessage().
- // Find new_task index in assistantMessageContent (may differ from assistantContent
- // due to text blocks being structured differently).
const executionNewTaskIndex = this.assistantMessageContent.findIndex(
(block) => block.type === "tool_use" && block.name === "new_task",
)
@@ -3528,7 +3423,6 @@ export class Task extends EventEmitter implements TaskLike {
this.assistantMessageContent.length = executionNewTaskIndex + 1
}
- // Pre-inject error tool_results for truncated tools
for (const tool of truncatedTools) {
if (tool.type === "tool_use" && (tool as Anthropic.ToolUseBlockParam).id) {
this.pushToolResultToUserContent({
@@ -3542,17 +3436,12 @@ export class Task extends EventEmitter implements TaskLike {
}
}
- // Save assistant message BEFORE executing tools
- // This is critical for new_task: when it triggers delegation, flushPendingToolResultsToHistory()
- // will save the user message with tool_results. The assistant message must already be in history
- // so that tool_result blocks appear AFTER their corresponding tool_use blocks.
+ // Save assistant message BEFORE executing tools.
await this.addToApiConversationHistory(
{ role: "assistant", content: assistantContent },
reasoningMessage || undefined,
)
this.assistantMessageSavedToHistory = true
-
- TelemetryService.instance.captureConversationMessage(this.taskId, "assistant")
}
// Present any partial blocks that were just completed.
diff --git a/src/core/task/__tests__/Task.dispose.test.ts b/src/core/task/__tests__/Task.dispose.test.ts
index 16bf3c91c2f..8bb407525c9 100644
--- a/src/core/task/__tests__/Task.dispose.test.ts
+++ b/src/core/task/__tests__/Task.dispose.test.ts
@@ -21,16 +21,6 @@ vi.mock("../../../api", () => ({
})),
}))
-// Mock TelemetryService
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCreated: vi.fn(),
- captureTaskRestarted: vi.fn(),
- },
- },
-}))
-
describe("Task dispose method", () => {
let mockProvider: any
let mockApiConfiguration: ProviderSettings
diff --git a/src/core/task/__tests__/Task.persistence.spec.ts b/src/core/task/__tests__/Task.persistence.spec.ts
index e73638d8ad3..d2af73ccb7c 100644
--- a/src/core/task/__tests__/Task.persistence.spec.ts
+++ b/src/core/task/__tests__/Task.persistence.spec.ts
@@ -5,7 +5,6 @@ import * as path from "path"
import * as vscode from "vscode"
import type { GlobalState, ProviderSettings } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
@@ -203,10 +202,6 @@ describe("Task persistence", () => {
beforeEach(() => {
vi.clearAllMocks()
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage") }
mockExtensionContext = {
diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts
index d9f546d4638..bc0da230923 100644
--- a/src/core/task/__tests__/Task.spec.ts
+++ b/src/core/task/__tests__/Task.spec.ts
@@ -7,7 +7,6 @@ import * as vscode from "vscode"
import { Anthropic } from "@anthropic-ai/sdk"
import type { GlobalState, ProviderSettings, ModelInfo } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
@@ -200,10 +199,6 @@ describe("Cline", () => {
let mockExtensionContext: vscode.ExtensionContext
beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
// Setup mock extension context
const storageUri = {
fsPath: path.join(os.tmpdir(), "test-storage"),
diff --git a/src/core/task/__tests__/Task.sticky-profile-race.spec.ts b/src/core/task/__tests__/Task.sticky-profile-race.spec.ts
index 38a3098b041..74beba7126c 100644
--- a/src/core/task/__tests__/Task.sticky-profile-race.spec.ts
+++ b/src/core/task/__tests__/Task.sticky-profile-race.spec.ts
@@ -6,25 +6,6 @@ import type { ProviderSettings } from "@roo-code/types"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- hasInstance: vi.fn().mockReturnValue(true),
- createInstance: vi.fn(),
- get instance() {
- return {
- captureTaskCreated: vi.fn(),
- captureTaskRestarted: vi.fn(),
- captureModeSwitch: vi.fn(),
- captureConversationMessage: vi.fn(),
- captureLlmCompletion: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- captureCodeActionUsed: vi.fn(),
- setProvider: vi.fn(),
- }
- },
- },
-}))
-
vi.mock("vscode", () => {
const mockDisposable = { dispose: vi.fn() }
const mockEventEmitter = { event: vi.fn(), fire: vi.fn() }
diff --git a/src/core/task/__tests__/Task.throttle.test.ts b/src/core/task/__tests__/Task.throttle.test.ts
index c9d78dc291a..8226ceec4a4 100644
--- a/src/core/task/__tests__/Task.throttle.test.ts
+++ b/src/core/task/__tests__/Task.throttle.test.ts
@@ -22,16 +22,6 @@ vi.mock("../../../api", () => ({
})),
}))
-// Mock TelemetryService
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCreated: vi.fn(),
- captureTaskRestarted: vi.fn(),
- },
- },
-}))
-
// Mock task persistence to avoid disk writes
vi.mock("../../task-persistence", () => ({
readApiMessages: vi.fn().mockResolvedValue([]),
diff --git a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts
index f19645d9697..fea0d5b790c 100644
--- a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts
+++ b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts
@@ -5,7 +5,6 @@ import * as path from "path"
import * as vscode from "vscode"
import type { GlobalState, ProviderSettings } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
@@ -159,10 +158,6 @@ describe("flushPendingToolResultsToHistory", () => {
let mockExtensionContext: vscode.ExtensionContext
beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
const storageUri = {
fsPath: path.join(os.tmpdir(), "test-storage"),
}
diff --git a/src/core/task/__tests__/grace-retry-errors.spec.ts b/src/core/task/__tests__/grace-retry-errors.spec.ts
index 283b402f69b..ffaeff52f45 100644
--- a/src/core/task/__tests__/grace-retry-errors.spec.ts
+++ b/src/core/task/__tests__/grace-retry-errors.spec.ts
@@ -5,7 +5,6 @@ import * as path from "path"
import * as vscode from "vscode"
import type { GlobalState, ProviderSettings } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
@@ -147,10 +146,6 @@ describe("Grace Retry Error Handling", () => {
let mockExtensionContext: vscode.ExtensionContext
beforeEach(() => {
- if (!TelemetryService.hasInstance()) {
- TelemetryService.createInstance([])
- }
-
const storageUri = {
fsPath: path.join(os.tmpdir(), "test-storage"),
}
diff --git a/src/core/task/__tests__/grounding-sources.test.ts b/src/core/task/__tests__/grounding-sources.test.ts
index fd82ef8bafe..67fc360aca9 100644
--- a/src/core/task/__tests__/grounding-sources.test.ts
+++ b/src/core/task/__tests__/grounding-sources.test.ts
@@ -51,29 +51,6 @@ vi.mock("vscode", () => ({
}))
// Mock other dependencies
-vi.mock("../../services/mcp/McpServerManager", () => ({
- McpServerManager: {
- getInstance: vi.fn().mockResolvedValue(null),
- },
-}))
-
-vi.mock("../../integrations/terminal/TerminalRegistry", () => ({
- TerminalRegistry: {
- releaseTerminalsForTask: vi.fn(),
- },
-}))
-
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCreated: vi.fn(),
- captureTaskRestarted: vi.fn(),
- captureConversationMessage: vi.fn(),
- captureLlmCompletion: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- },
- },
-}))
vi.mock("@roo-code/cloud", () => ({
CloudService: {
diff --git a/src/core/task/__tests__/reasoning-preservation.test.ts b/src/core/task/__tests__/reasoning-preservation.test.ts
index ea70a26bd3c..20486444dfe 100644
--- a/src/core/task/__tests__/reasoning-preservation.test.ts
+++ b/src/core/task/__tests__/reasoning-preservation.test.ts
@@ -51,29 +51,6 @@ vi.mock("vscode", () => ({
}))
// Mock other dependencies
-vi.mock("../../services/mcp/McpServerManager", () => ({
- McpServerManager: {
- getInstance: vi.fn().mockResolvedValue(null),
- },
-}))
-
-vi.mock("../../integrations/terminal/TerminalRegistry", () => ({
- TerminalRegistry: {
- releaseTerminalsForTask: vi.fn(),
- },
-}))
-
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCreated: vi.fn(),
- captureTaskRestarted: vi.fn(),
- captureConversationMessage: vi.fn(),
- captureLlmCompletion: vi.fn(),
- captureConsecutiveMistakeError: vi.fn(),
- },
- },
-}))
vi.mock("@roo-code/cloud", () => ({
CloudService: {
diff --git a/src/core/task/__tests__/validateToolResultIds.spec.ts b/src/core/task/__tests__/validateToolResultIds.spec.ts
index 0926e899aad..cc5bb36a4f9 100644
--- a/src/core/task/__tests__/validateToolResultIds.spec.ts
+++ b/src/core/task/__tests__/validateToolResultIds.spec.ts
@@ -1,21 +1,10 @@
import { Anthropic } from "@anthropic-ai/sdk"
-import { TelemetryService } from "@roo-code/telemetry"
import {
validateAndFixToolResultIds,
ToolResultIdMismatchError,
MissingToolResultError,
} from "../validateToolResultIds"
-// Mock TelemetryService
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- hasInstance: vi.fn(() => true),
- instance: {
- captureException: vi.fn(),
- },
- },
-}))
-
describe("validateAndFixToolResultIds", () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -397,7 +386,7 @@ describe("validateAndFixToolResultIds", () => {
})
it("should filter out duplicate tool_results when one already has a valid ID", () => {
- // This is the exact scenario from the PostHog error:
+ // This is the exact mismatched tool result scenario:
// 2 tool_results (call_08230257, call_55577629), 1 tool_use (call_55577629)
const assistantMessage: Anthropic.MessageParam = {
role: "assistant",
@@ -773,85 +762,6 @@ describe("validateAndFixToolResultIds", () => {
})
})
- describe("telemetry", () => {
- it("should call captureException for both missing and mismatch when there is a mismatch", () => {
- const assistantMessage: Anthropic.MessageParam = {
- role: "assistant",
- content: [
- {
- type: "tool_use",
- id: "correct-id",
- name: "read_file",
- input: { path: "test.txt" },
- },
- ],
- }
-
- const userMessage: Anthropic.MessageParam = {
- role: "user",
- content: [
- {
- type: "tool_result",
- tool_use_id: "wrong-id",
- content: "Content",
- },
- ],
- }
-
- validateAndFixToolResultIds(userMessage, [assistantMessage])
-
- // A mismatch also triggers missing detection since the wrong-id doesn't match any tool_use
- expect(TelemetryService.instance.captureException).toHaveBeenCalledTimes(2)
- expect(TelemetryService.instance.captureException).toHaveBeenCalledWith(
- expect.any(MissingToolResultError),
- expect.objectContaining({
- missingToolUseIds: ["correct-id"],
- existingToolResultIds: ["wrong-id"],
- toolUseCount: 1,
- toolResultCount: 1,
- }),
- )
- expect(TelemetryService.instance.captureException).toHaveBeenCalledWith(
- expect.any(ToolResultIdMismatchError),
- expect.objectContaining({
- toolResultIds: ["wrong-id"],
- toolUseIds: ["correct-id"],
- toolResultCount: 1,
- toolUseCount: 1,
- }),
- )
- })
-
- it("should not call captureException when IDs match", () => {
- const assistantMessage: Anthropic.MessageParam = {
- role: "assistant",
- content: [
- {
- type: "tool_use",
- id: "tool-123",
- name: "read_file",
- input: { path: "test.txt" },
- },
- ],
- }
-
- const userMessage: Anthropic.MessageParam = {
- role: "user",
- content: [
- {
- type: "tool_result",
- tool_use_id: "tool-123",
- content: "Content",
- },
- ],
- }
-
- validateAndFixToolResultIds(userMessage, [assistantMessage])
-
- expect(TelemetryService.instance.captureException).not.toHaveBeenCalled()
- })
- })
-
describe("ToolResultIdMismatchError", () => {
it("should create error with correct properties", () => {
const error = new ToolResultIdMismatchError(
@@ -881,117 +791,4 @@ describe("validateAndFixToolResultIds", () => {
expect(error.existingToolResultIds).toEqual(["existing-result-1"])
})
})
-
- describe("telemetry for missing tool_results", () => {
- it("should call captureException when tool_results are missing", () => {
- const assistantMessage: Anthropic.MessageParam = {
- role: "assistant",
- content: [
- {
- type: "tool_use",
- id: "tool-123",
- name: "read_file",
- input: { path: "test.txt" },
- },
- ],
- }
-
- const userMessage: Anthropic.MessageParam = {
- role: "user",
- content: [
- {
- type: "text",
- text: "No tool results here",
- },
- ],
- }
-
- validateAndFixToolResultIds(userMessage, [assistantMessage])
-
- expect(TelemetryService.instance.captureException).toHaveBeenCalledTimes(1)
- expect(TelemetryService.instance.captureException).toHaveBeenCalledWith(
- expect.any(MissingToolResultError),
- expect.objectContaining({
- missingToolUseIds: ["tool-123"],
- existingToolResultIds: [],
- toolUseCount: 1,
- toolResultCount: 0,
- }),
- )
- })
-
- it("should call captureException twice when both mismatch and missing occur", () => {
- const assistantMessage: Anthropic.MessageParam = {
- role: "assistant",
- content: [
- {
- type: "tool_use",
- id: "tool-1",
- name: "read_file",
- input: { path: "a.txt" },
- },
- {
- type: "tool_use",
- id: "tool-2",
- name: "read_file",
- input: { path: "b.txt" },
- },
- ],
- }
-
- const userMessage: Anthropic.MessageParam = {
- role: "user",
- content: [
- {
- type: "tool_result",
- tool_use_id: "wrong-id", // Wrong ID (mismatch)
- content: "Content",
- },
- // Missing tool_result for tool-2
- ],
- }
-
- validateAndFixToolResultIds(userMessage, [assistantMessage])
-
- // Should be called twice: once for missing, once for mismatch
- expect(TelemetryService.instance.captureException).toHaveBeenCalledTimes(2)
- expect(TelemetryService.instance.captureException).toHaveBeenCalledWith(
- expect.any(MissingToolResultError),
- expect.any(Object),
- )
- expect(TelemetryService.instance.captureException).toHaveBeenCalledWith(
- expect.any(ToolResultIdMismatchError),
- expect.any(Object),
- )
- })
-
- it("should not call captureException for missing when all tool_results exist", () => {
- const assistantMessage: Anthropic.MessageParam = {
- role: "assistant",
- content: [
- {
- type: "tool_use",
- id: "tool-123",
- name: "read_file",
- input: { path: "test.txt" },
- },
- ],
- }
-
- const userMessage: Anthropic.MessageParam = {
- role: "user",
- content: [
- {
- type: "tool_result",
- tool_use_id: "tool-123",
- content: "Content",
- },
- ],
- }
-
- validateAndFixToolResultIds(userMessage, [assistantMessage])
-
- expect(TelemetryService.instance.captureException).not.toHaveBeenCalled()
- })
- })
})
diff --git a/src/core/task/validateToolResultIds.ts b/src/core/task/validateToolResultIds.ts
index a966d429ed5..0d366cd6036 100644
--- a/src/core/task/validateToolResultIds.ts
+++ b/src/core/task/validateToolResultIds.ts
@@ -1,10 +1,9 @@
import { Anthropic } from "@anthropic-ai/sdk"
-import { TelemetryService } from "@roo-code/telemetry"
import { findLastIndex } from "../../shared/array"
/**
* Custom error class for tool result ID mismatches.
- * Used for structured error tracking via PostHog.
+ * Used for structured error tracking via external service.
*/
export class ToolResultIdMismatchError extends Error {
constructor(
@@ -19,7 +18,7 @@ export class ToolResultIdMismatchError extends Error {
/**
* Custom error class for missing tool results.
- * Used for structured error tracking via PostHog when tool_use blocks
+ * Used for structured error tracking via external service when tool_use blocks
* don't have corresponding tool_result blocks.
*/
export class MissingToolResultError extends Error {
@@ -131,40 +130,6 @@ export function validateAndFixToolResultIds(
const toolResultIdList = toolResults.map((r) => r.tool_use_id)
const toolUseIdList = toolUseBlocks.map((b) => b.id)
- // Report missing tool_results to PostHog error tracking
- if (missingToolUseIds.length > 0 && TelemetryService.hasInstance()) {
- TelemetryService.instance.captureException(
- new MissingToolResultError(
- `Detected missing tool_result blocks. Missing tool_use IDs: [${missingToolUseIds.join(", ")}], existing tool_result IDs: [${toolResultIdList.join(", ")}]`,
- missingToolUseIds,
- toolResultIdList,
- ),
- {
- missingToolUseIds,
- existingToolResultIds: toolResultIdList,
- toolUseCount: toolUseBlocks.length,
- toolResultCount: toolResults.length,
- },
- )
- }
-
- // Report ID mismatches to PostHog error tracking
- if (hasInvalidIds && TelemetryService.hasInstance()) {
- TelemetryService.instance.captureException(
- new ToolResultIdMismatchError(
- `Detected tool_result ID mismatch. tool_result IDs: [${toolResultIdList.join(", ")}], tool_use IDs: [${toolUseIdList.join(", ")}]`,
- toolResultIdList,
- toolUseIdList,
- ),
- {
- toolResultIds: toolResultIdList,
- toolUseIds: toolUseIdList,
- toolResultCount: toolResults.length,
- toolUseCount: toolUseBlocks.length,
- },
- )
- }
-
// Match tool_results to tool_uses by position and fix incorrect IDs
const usedToolUseIds = new Set()
const contentArray = userMessage.content as Anthropic.Messages.ContentBlockParam[]
diff --git a/src/core/tools/ApplyDiffTool.ts b/src/core/tools/ApplyDiffTool.ts
index 3b664b3bd22..24968c0451e 100644
--- a/src/core/tools/ApplyDiffTool.ts
+++ b/src/core/tools/ApplyDiffTool.ts
@@ -2,7 +2,6 @@ import path from "path"
import fs from "fs/promises"
import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { getReadablePath } from "../../utils/path"
import { Task } from "../task/Task"
@@ -85,7 +84,6 @@ export class ApplyDiffTool extends BaseTool<"apply_diff"> {
const currentCount = (task.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1
task.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount)
let formattedError = ""
- TelemetryService.instance.captureDiffApplicationError(task.taskId, currentCount)
if (diffResult.failParts && diffResult.failParts.length > 0) {
for (const failPart of diffResult.failParts) {
diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts
index a70576d75f2..16e0428120c 100644
--- a/src/core/tools/AttemptCompletionTool.ts
+++ b/src/core/tools/AttemptCompletionTool.ts
@@ -1,7 +1,6 @@
import * as vscode from "vscode"
import { RooCodeEventName, type HistoryItem } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../task/Task"
import { formatResponse } from "../prompts/responses"
@@ -200,8 +199,6 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> {
// Force final token usage update before emitting TaskCompleted.
// This ensures the latest stats are captured regardless of throttle timer.
task.emitFinalTokenUsageUpdate()
-
- TelemetryService.instance.captureTaskCompleted(task.taskId)
task.emit(RooCodeEventName.TaskCompleted, task.taskId, task.getTokenUsage(), task.toolUsage)
}
}
diff --git a/src/core/tools/ExecuteCommandTool.ts b/src/core/tools/ExecuteCommandTool.ts
index 8fcb917b134..6fe1f6c049c 100644
--- a/src/core/tools/ExecuteCommandTool.ts
+++ b/src/core/tools/ExecuteCommandTool.ts
@@ -5,7 +5,6 @@ import * as vscode from "vscode"
import delay from "delay"
import { CommandExecutionStatus, DEFAULT_TERMINAL_OUTPUT_PREVIEW_SIZE, PersistedCommandOutput } from "@roo-code/types"
-import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../task/Task"
@@ -359,13 +358,6 @@ export async function executeCommandInTerminal(
},
}
- if (terminalProvider === "vscode") {
- callbacks.onNoShellIntegration = async (error: string) => {
- TelemetryService.instance.captureShellIntegrationError(task.taskId)
- shellIntegrationError = error
- }
- }
-
const terminal = await TerminalRegistry.getOrCreateTerminal(workingDir, task.taskId, terminalProvider)
if (terminal instanceof Terminal) {
diff --git a/src/core/tools/__tests__/attemptCompletionTool.spec.ts b/src/core/tools/__tests__/attemptCompletionTool.spec.ts
index c66146e4c23..06a1b1d3953 100644
--- a/src/core/tools/__tests__/attemptCompletionTool.spec.ts
+++ b/src/core/tools/__tests__/attemptCompletionTool.spec.ts
@@ -11,17 +11,6 @@ vi.mock("../../prompts/responses", () => ({
},
}))
-const { mockCaptureTaskCompleted } = vi.hoisted(() => ({
- mockCaptureTaskCompleted: vi.fn(),
-}))
-vi.mock("@roo-code/telemetry", () => ({
- TelemetryService: {
- instance: {
- captureTaskCompleted: mockCaptureTaskCompleted,
- },
- },
-}))
-
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
@@ -52,7 +41,6 @@ describe("attemptCompletionTool", () => {
let mockGetConfiguration: ReturnType
beforeEach(() => {
- mockCaptureTaskCompleted.mockReset()
mockPushToolResult = vi.fn()
mockAskApproval = vi.fn()
mockHandleError = vi.fn()
@@ -506,7 +494,6 @@ describe("attemptCompletionTool", () => {
await attemptCompletionTool.handle(mockTask as Task, block, callbacks)
expect(mockHandleError).not.toHaveBeenCalled()
- expect(mockCaptureTaskCompleted).toHaveBeenCalledWith("task_1")
expect(mockTask.emit).toHaveBeenCalledWith(
RooCodeEventName.TaskCompleted,
"task_1",
@@ -541,7 +528,6 @@ describe("attemptCompletionTool", () => {
await attemptCompletionTool.handle(mockTask as Task, block, callbacks)
expect(mockHandleError).not.toHaveBeenCalled()
- expect(mockCaptureTaskCompleted).not.toHaveBeenCalled()
expect(mockTask.emit).not.toHaveBeenCalledWith(
RooCodeEventName.TaskCompleted,
expect.anything(),
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index cc768d595d1..541e11577eb 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -17,13 +17,6 @@ import {
type ProviderSettings,
type RooCodeSettings,
type ProviderSettingsEntry,
- type StaticAppProperties,
- type DynamicAppProperties,
- type CloudAppProperties,
- type TaskProperties,
- type GitProperties,
- type TelemetryProperties,
- type TelemetryPropertiesProvider,
type CodeActionId,
type CodeActionName,
type TerminalActionId,
@@ -48,7 +41,6 @@ import {
isRetiredProvider,
} from "@roo-code/types"
import { aggregateTaskCostsRecursive, type AggregatedCosts } from "./aggregateTaskCosts"
-import { TelemetryService } from "@roo-code/telemetry"
import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
import { Package } from "../../shared/package"
@@ -124,7 +116,7 @@ interface PendingEditOperation {
export class ClineProvider
extends EventEmitter
- implements vscode.WebviewViewProvider, TelemetryPropertiesProvider, TaskProviderLike
+ implements vscode.WebviewViewProvider, TaskProviderLike
{
// Used in package.json as the view's id. This value cannot be changed due
// to how VSCode caches views based on their id, and updating the id would
@@ -196,13 +188,6 @@ export class ClineProvider
this.log(`Failed to initialize TaskHistoryStore: ${error}`)
})
- // Start configuration loading (which might trigger indexing) in the background.
- // Don't await, allowing activation to continue immediately.
-
- // Register this provider with the telemetry service to enable it to add
- // properties like mode and provider.
- TelemetryService.instance.setProvider(this)
-
this._workspaceTracker = new WorkspaceTracker(this)
this.providerSettingsManager = new ProviderSettingsManager(this.context)
@@ -761,9 +746,6 @@ export class ClineProvider
promptType: CodeActionName,
params: Record,
): Promise {
- // Capture telemetry for code action usage
- TelemetryService.instance.captureCodeActionUsed(promptType)
-
const visibleProvider = await ClineProvider.getInstance()
if (!visibleProvider) {
@@ -793,8 +775,6 @@ export class ClineProvider
promptType: TerminalActionPromptType,
params: Record,
): Promise {
- TelemetryService.instance.captureCodeActionUsed(promptType)
-
const visibleProvider = await ClineProvider.getInstance()
if (!visibleProvider) {
@@ -1261,8 +1241,8 @@ export class ClineProvider
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
`img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:`,
`media-src ${webview.cspSource}`,
- `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
- `connect-src ${webview.cspSource} ${openRouterDomain} https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
+ `script-src 'unsafe-eval' ${webview.cspSource} https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
+ `connect-src ${webview.cspSource} ${openRouterDomain} https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
]
return /*html*/ `
@@ -1350,7 +1330,7 @@ export class ClineProvider
-
+