feat: add custom OpenTelemetry collector endpoints support

- Add OpenTelemetryClient class to packages/telemetry for sending traces to custom OTEL endpoints
- Add OTEL configuration fields (otelEnabled, otelEndpoints) to global settings schema
- Create UI components in settings view for managing OTEL endpoints with add/remove/edit capabilities
- Integrate OTEL client initialization in extension startup
- Add message handlers for OTEL settings updates between webview and extension
- Include comprehensive test coverage for OpenTelemetryClient
- Support multiple endpoints with custom headers and enable/disable toggles per endpoint

Implements #7020
This commit is contained in:
Roo Code 2025-08-12 21:12:40 +00:00
parent 245bc2a2e6
commit bb00d6c114
13 changed files with 1467 additions and 8 deletions

View file

@ -11,6 +11,13 @@
"clean": "rimraf dist .turbo"
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.56.0",
"@opentelemetry/instrumentation": "^0.56.0",
"@opentelemetry/resources": "^1.29.0",
"@opentelemetry/sdk-trace-base": "^1.29.0",
"@opentelemetry/sdk-trace-node": "^1.29.0",
"@opentelemetry/semantic-conventions": "^1.29.0",
"@roo-code/types": "workspace:^",
"posthog-node": "^5.0.0",
"zod": "^3.25.61"

View file

@ -0,0 +1,209 @@
import { trace, SpanStatusCode, Tracer } from "@opentelemetry/api"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
import { Resource } from "@opentelemetry/resources"
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions"
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"
import { BatchSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base"
import { registerInstrumentations } from "@opentelemetry/instrumentation"
import { type TelemetryEvent } from "@roo-code/types"
import { BaseTelemetryClient } from "./BaseTelemetryClient"
// Conditionally import vscode only when not in test environment
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let vscode: any
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
vscode = require("vscode")
} catch {
// In test environment, vscode is not available
vscode = {
extensions: {
getExtension: () => ({ packageJSON: { version: "test-version" } }),
},
workspace: {
getConfiguration: () => ({
get: () => "all",
}),
},
}
}
export interface OtelEndpoint {
url: string
headers?: Record<string, string>
enabled: boolean
}
/**
* OpenTelemetryClient handles telemetry event tracking using OpenTelemetry.
* Supports sending traces to multiple custom endpoints in addition to internal endpoints.
* Respects user privacy settings and VSCode's global telemetry configuration.
*/
export class OpenTelemetryClient extends BaseTelemetryClient {
private tracer: Tracer | null = null
private provider: NodeTracerProvider | null = null
private endpoints: OtelEndpoint[] = []
private isInitialized = false
constructor(debug = false) {
super(undefined, debug)
}
/**
* Initialize or reinitialize the OpenTelemetry provider with the given endpoints
* @param endpoints Array of OTEL collector endpoints to send traces to
*/
public async initialize(endpoints: OtelEndpoint[]): Promise<void> {
try {
// Shutdown existing provider if any
if (this.provider) {
await this.shutdown()
}
this.endpoints = endpoints.filter((ep) => ep.enabled)
// Create resource with service information
const version =
vscode?.extensions?.getExtension?.("rooveterinaryinc.roo-cline")?.packageJSON?.version || "unknown"
const resource = new Resource({
[ATTR_SERVICE_NAME]: "roo-code",
[ATTR_SERVICE_VERSION]: version,
})
// Create provider
this.provider = new NodeTracerProvider({
resource,
})
// Add exporters for each endpoint
for (const endpoint of this.endpoints) {
const exporter = new OTLPTraceExporter({
url: endpoint.url,
headers: endpoint.headers || {},
})
// Use BatchSpanProcessor for better performance
this.provider.addSpanProcessor(new BatchSpanProcessor(exporter))
}
// Add console exporter in debug mode
if (this.debug) {
this.provider.addSpanProcessor(new BatchSpanProcessor(new ConsoleSpanExporter()))
}
// Register the provider
this.provider.register()
// Register instrumentations
registerInstrumentations({
instrumentations: [],
})
// Only get tracer if we have endpoints
if (this.endpoints.length > 0) {
// Get tracer with version
this.tracer = trace.getTracer("roo-code-telemetry", version)
this.isInitialized = true
}
if (this.debug) {
console.info(`[OpenTelemetryClient#initialize] Initialized with ${this.endpoints.length} endpoints`)
}
} catch (error) {
console.error("[OpenTelemetry] Failed to initialize:", error)
// Don't throw - just log the error
}
}
/**
* Update endpoints configuration
* This will reinitialize the provider with the new endpoints
* @param endpoints New array of OTEL collector endpoints
*/
public async updateEndpoints(endpoints: OtelEndpoint[]): Promise<void> {
await this.initialize(endpoints)
}
public override async capture(event: TelemetryEvent): Promise<void> {
if (!this.isTelemetryEnabled() || !this.isInitialized || !this.tracer) {
if (this.debug) {
console.info(
`[OpenTelemetryClient#capture] Skipping event: ${event.event} (enabled: ${this.isTelemetryEnabled()}, initialized: ${this.isInitialized})`,
)
}
return
}
try {
if (this.debug) {
console.info(`[OpenTelemetryClient#capture] ${event.event}`)
}
// Get event properties
const properties = await this.getEventProperties(event)
// Create a span for the event
const span = this.tracer.startSpan(event.event)
// Set attributes after creating the span
if (properties && Object.keys(properties).length > 0) {
span.setAttributes(properties)
}
// Set span status to OK and end it immediately since these are point-in-time events
span.setStatus({ code: SpanStatusCode.OK })
span.end()
} catch (error) {
console.error("[OpenTelemetry] Failed to capture event:", error)
// Don't throw - just log the error
}
}
/**
* 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") || "all"
const globalTelemetryEnabled = telemetryLevel === "all"
// We only enable telemetry if global vscode telemetry is enabled.
if (globalTelemetryEnabled) {
this.telemetryEnabled = didUserOptIn
}
if (this.debug) {
console.info(`[OpenTelemetryClient#updateTelemetryState] Telemetry enabled: ${this.telemetryEnabled}`)
}
}
public override async shutdown(): Promise<void> {
if (this.provider) {
try {
await this.provider.shutdown()
} catch (error) {
console.error("[OpenTelemetry] Failed to shutdown:", error)
// Don't throw - just log the error
}
this.provider = null
this.tracer = null
this.isInitialized = false
}
}
/**
* Get the currently configured endpoints
* @returns Array of configured OTEL endpoints
*/
public getEndpoints(): OtelEndpoint[] {
return [...this.endpoints]
}
}

View file

@ -0,0 +1,490 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// npx vitest run src/__tests__/OpenTelemetryClient.test.ts
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"
import { registerInstrumentations } from "@opentelemetry/instrumentation"
import { trace, context } from "@opentelemetry/api"
import { type TelemetryPropertiesProvider, TelemetryEventName } from "@roo-code/types"
import { OpenTelemetryClient } from "../OpenTelemetryClient"
// Define OtelEndpoint type locally for tests
interface OtelEndpoint {
url: string
headers?: Record<string, string>
enabled: boolean
}
// Mock OpenTelemetry modules
vi.mock("@opentelemetry/sdk-trace-node")
vi.mock("@opentelemetry/exporter-trace-otlp-http")
vi.mock("@opentelemetry/sdk-trace-base")
vi.mock("@opentelemetry/instrumentation")
vi.mock("@opentelemetry/api")
describe("OpenTelemetryClient", () => {
let mockProvider: any
let mockTracer: any
let mockSpan: any
let mockExporter: any
let mockProcessor: any
beforeEach(() => {
vi.clearAllMocks()
// Mock span
mockSpan = {
setAttributes: vi.fn().mockReturnThis(),
setStatus: vi.fn().mockReturnThis(),
recordException: vi.fn().mockReturnThis(),
end: vi.fn(),
}
// Mock tracer
mockTracer = {
startSpan: vi.fn().mockReturnValue(mockSpan),
}
// Mock provider
mockProvider = {
register: vi.fn(),
addSpanProcessor: vi.fn(),
shutdown: vi.fn().mockResolvedValue(undefined),
}
// Mock exporter
mockExporter = {
shutdown: vi.fn().mockResolvedValue(undefined),
}
// Mock processor
mockProcessor = {
shutdown: vi.fn().mockResolvedValue(undefined),
}
// Setup mocks
;(NodeTracerProvider as any).mockImplementation(() => mockProvider)
;(OTLPTraceExporter as any).mockImplementation(() => mockExporter)
;(BatchSpanProcessor as any).mockImplementation(() => mockProcessor)
;(registerInstrumentations as any).mockImplementation(() => {})
;(trace.getTracer as any) = vi.fn().mockReturnValue(mockTracer)
;(context.with as any) = vi.fn((ctx, fn) => fn())
;(context.active as any) = vi.fn().mockReturnValue({})
})
describe("initialize", () => {
it("should initialize with multiple endpoints", async () => {
const client = new OpenTelemetryClient()
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: { "x-api-key": "test-key-1" },
enabled: true,
},
{
url: "http://example.com:4318/v1/traces",
headers: { "x-api-key": "test-key-2" },
enabled: true,
},
{
url: "http://disabled.com:4318/v1/traces",
headers: {},
enabled: false, // This one should be filtered out
},
]
await client.initialize(endpoints)
// Should create provider
expect(NodeTracerProvider).toHaveBeenCalledTimes(1)
expect(mockProvider.register).toHaveBeenCalledTimes(1)
// Should create exporters only for enabled endpoints
expect(OTLPTraceExporter).toHaveBeenCalledTimes(2)
expect(OTLPTraceExporter).toHaveBeenCalledWith({
url: "http://localhost:4318/v1/traces",
headers: { "x-api-key": "test-key-1" },
})
expect(OTLPTraceExporter).toHaveBeenCalledWith({
url: "http://example.com:4318/v1/traces",
headers: { "x-api-key": "test-key-2" },
})
// Should create processors for each enabled endpoint
expect(BatchSpanProcessor).toHaveBeenCalledTimes(2)
expect(mockProvider.addSpanProcessor).toHaveBeenCalledTimes(2)
// Should register instrumentations
expect(registerInstrumentations).toHaveBeenCalledTimes(1)
})
it("should handle empty endpoints array", async () => {
const client = new OpenTelemetryClient()
await client.initialize([])
// Should still create provider but no exporters
expect(NodeTracerProvider).toHaveBeenCalledTimes(1)
expect(OTLPTraceExporter).not.toHaveBeenCalled()
expect(BatchSpanProcessor).not.toHaveBeenCalled()
})
it("should handle initialization errors gracefully", async () => {
const client = new OpenTelemetryClient()
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
// Make provider throw an error
;(NodeTracerProvider as any).mockImplementation(() => {
throw new Error("Provider initialization failed")
})
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
expect(consoleErrorSpy).toHaveBeenCalledWith("[OpenTelemetry] Failed to initialize:", expect.any(Error))
consoleErrorSpy.mockRestore()
})
})
describe("capture", () => {
it("should create and end a span for captured events", async () => {
const client = new OpenTelemetryClient()
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
client.updateTelemetryState(true)
const mockTelemetryProvider: TelemetryPropertiesProvider = {
getTelemetryProperties: vi.fn().mockResolvedValue({
appVersion: "1.0.0",
vscodeVersion: "1.60.0",
platform: "darwin",
mode: "code",
}),
}
client.setProvider(mockTelemetryProvider)
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: {
customProp: "value",
duration: 1000,
},
})
// Should get tracer
expect(trace.getTracer).toHaveBeenCalledWith("roo-code-telemetry", expect.any(String))
// Should start span with event name
expect(mockTracer.startSpan).toHaveBeenCalledWith(TelemetryEventName.TASK_CREATED)
// Should set attributes from merged properties
expect(mockSpan.setAttributes).toHaveBeenCalledWith({
appVersion: "1.0.0",
vscodeVersion: "1.60.0",
platform: "darwin",
mode: "code",
customProp: "value",
duration: 1000,
})
// Should end the span
expect(mockSpan.end).toHaveBeenCalledTimes(1)
})
it("should not capture when telemetry is disabled", async () => {
const client = new OpenTelemetryClient()
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
// Clear the mock calls from initialization
vi.clearAllMocks()
client.updateTelemetryState(false) // Disable telemetry
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: { test: "value" },
})
// Should not create any spans when telemetry is disabled
expect(mockTracer.startSpan).not.toHaveBeenCalled()
})
it("should not capture when not initialized", async () => {
const client = new OpenTelemetryClient()
client.updateTelemetryState(true)
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: { test: "value" },
})
// Should not create any spans
expect(trace.getTracer).not.toHaveBeenCalled()
expect(mockTracer.startSpan).not.toHaveBeenCalled()
})
it("should handle capture errors gracefully", async () => {
const client = new OpenTelemetryClient()
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
client.updateTelemetryState(true)
// Make startSpan throw an error
mockTracer.startSpan.mockImplementation(() => {
throw new Error("Failed to start span")
})
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: { test: "value" },
})
expect(consoleErrorSpy).toHaveBeenCalledWith("[OpenTelemetry] Failed to capture event:", expect.any(Error))
consoleErrorSpy.mockRestore()
})
it("should handle provider errors gracefully", async () => {
const client = new OpenTelemetryClient()
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
client.updateTelemetryState(true)
const mockTelemetryProvider: TelemetryPropertiesProvider = {
getTelemetryProperties: vi.fn().mockRejectedValue(new Error("Provider error")),
}
client.setProvider(mockTelemetryProvider)
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: { customProp: "value" },
})
// Should still create span with event properties only
expect(mockTracer.startSpan).toHaveBeenCalled()
expect(mockSpan.setAttributes).toHaveBeenCalledWith({ customProp: "value" })
expect(mockSpan.end).toHaveBeenCalled()
consoleErrorSpy.mockRestore()
})
})
describe("updateTelemetryState", () => {
it("should enable telemetry when set to true", () => {
const client = new OpenTelemetryClient()
client.updateTelemetryState(true)
expect(client.isTelemetryEnabled()).toBe(true)
})
it("should disable telemetry when set to false", () => {
const client = new OpenTelemetryClient()
client.updateTelemetryState(false)
expect(client.isTelemetryEnabled()).toBe(false)
})
})
describe("setProvider", () => {
it("should set the telemetry properties provider", () => {
const client = new OpenTelemetryClient()
const mockTelemetryProvider: TelemetryPropertiesProvider = {
getTelemetryProperties: vi.fn(),
}
client.setProvider(mockTelemetryProvider)
// Verify provider is set by attempting to capture with it
const getProviderSpy = vi.spyOn(mockTelemetryProvider, "getTelemetryProperties")
getProviderSpy.mockResolvedValue({
appName: "test-app",
appVersion: "1.0.0",
vscodeVersion: "1.60.0",
platform: "darwin",
editorName: "vscode",
language: "en",
mode: "code",
})
// Initialize and enable to allow capture
client.updateTelemetryState(true)
// Provider should be used during capture (though capture won't complete without initialization)
// This is just to verify the provider was set
expect(getProviderSpy).not.toHaveBeenCalled() // Not called yet since we haven't captured
})
})
describe("shutdown", () => {
it("should shutdown the provider when initialized", async () => {
const client = new OpenTelemetryClient()
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
await client.shutdown()
expect(mockProvider.shutdown).toHaveBeenCalledTimes(1)
})
it("should handle shutdown when not initialized", async () => {
const client = new OpenTelemetryClient()
// Should not throw
await expect(client.shutdown()).resolves.toBeUndefined()
})
it("should handle shutdown errors gracefully", async () => {
const client = new OpenTelemetryClient()
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
// Make shutdown throw an error
mockProvider.shutdown.mockRejectedValue(new Error("Shutdown failed"))
await client.shutdown()
expect(consoleErrorSpy).toHaveBeenCalledWith("[OpenTelemetry] Failed to shutdown:", expect.any(Error))
consoleErrorSpy.mockRestore()
})
})
describe("integration scenarios", () => {
it("should handle multiple captures in sequence", async () => {
const client = new OpenTelemetryClient()
const endpoints: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints)
client.updateTelemetryState(true)
// Capture multiple events
await client.capture({
event: TelemetryEventName.TASK_CREATED,
properties: { taskId: "1" },
})
await client.capture({
event: TelemetryEventName.MODE_SWITCH,
properties: { from: "code", to: "debug" },
})
await client.capture({
event: TelemetryEventName.TASK_COMPLETED,
properties: { taskId: "1", duration: 5000 },
})
// Should create 3 spans
expect(mockTracer.startSpan).toHaveBeenCalledTimes(3)
expect(mockSpan.end).toHaveBeenCalledTimes(3)
// Verify different event names were used
expect(mockTracer.startSpan).toHaveBeenNthCalledWith(1, TelemetryEventName.TASK_CREATED)
expect(mockTracer.startSpan).toHaveBeenNthCalledWith(2, TelemetryEventName.MODE_SWITCH)
expect(mockTracer.startSpan).toHaveBeenNthCalledWith(3, TelemetryEventName.TASK_COMPLETED)
})
it("should reinitialize with new endpoints", async () => {
const client = new OpenTelemetryClient()
// First initialization
const endpoints1: OtelEndpoint[] = [
{
url: "http://localhost:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints1)
// Clear mocks
vi.clearAllMocks()
// Second initialization with different endpoints
const endpoints2: OtelEndpoint[] = [
{
url: "http://example.com:4318/v1/traces",
headers: { "x-api-key": "new-key" },
enabled: true,
},
{
url: "http://another.com:4318/v1/traces",
headers: {},
enabled: true,
},
]
await client.initialize(endpoints2)
// Should create new provider and exporters
expect(NodeTracerProvider).toHaveBeenCalledTimes(1)
expect(OTLPTraceExporter).toHaveBeenCalledTimes(2)
expect(BatchSpanProcessor).toHaveBeenCalledTimes(2)
})
})
})

View file

@ -1,3 +1,4 @@
export * from "./BaseTelemetryClient"
export * from "./PostHogTelemetryClient"
export * from "./OpenTelemetryClient"
export * from "./TelemetryService"

View file

@ -143,6 +143,18 @@ export const globalSettingsSchema = z.object({
remoteControlEnabled: z.boolean().optional(),
// OpenTelemetry configuration
otelEnabled: z.boolean().optional(),
otelEndpoints: z
.array(
z.object({
url: z.string(),
headers: z.record(z.string(), z.string()).optional(),
enabled: z.boolean(),
}),
)
.optional(),
mode: z.string().optional(),
modeApiConfigs: z.record(z.string(), z.string()).optional(),
customModes: z.array(modeConfigSchema).optional(),

397
pnpm-lock.yaml generated
View file

@ -174,7 +174,7 @@ importers:
version: 0.518.0(react@18.3.1)
next:
specifier: ^15.2.5
version: 15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 15.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -277,7 +277,7 @@ importers:
version: 0.518.0(react@18.3.1)
next:
specifier: ^15.2.5
version: 15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 15.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -401,7 +401,7 @@ importers:
version: 0.13.0
drizzle-orm:
specifier: ^0.44.1
version: 0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
version: 0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7)
execa:
specifier: ^9.6.0
version: 9.6.0
@ -482,6 +482,27 @@ importers:
packages/telemetry:
dependencies:
'@opentelemetry/api':
specifier: ^1.9.0
version: 1.9.0
'@opentelemetry/exporter-trace-otlp-http':
specifier: ^0.56.0
version: 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation':
specifier: ^0.56.0
version: 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources':
specifier: ^1.29.0
version: 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base':
specifier: ^1.29.0
version: 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-node':
specifier: ^1.29.0
version: 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions':
specifier: ^1.29.0
version: 1.36.0
'@roo-code/types':
specifier: workspace:^
version: link:../types
@ -2277,6 +2298,118 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@opentelemetry/api-logs@0.56.0':
resolution: {integrity: sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g==}
engines: {node: '>=14'}
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@opentelemetry/context-async-hooks@1.30.1':
resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/core@1.29.0':
resolution: {integrity: sha512-gmT7vAreXl0DTHD2rVZcw3+l2g84+5XiHIqdBUxXbExymPCvSsGOpiwMmn8nkiJur28STV31wnhIDrzWDPzjfA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/core@1.30.1':
resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/exporter-trace-otlp-http@0.56.0':
resolution: {integrity: sha512-vqVuJvcwameA0r0cNrRzrZqPLB0otS+95g0XkZdiKOXUo81wYdY6r4kyrwz4nSChqTBEFm0lqi/H2OWGboOa6g==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation@0.56.0':
resolution: {integrity: sha512-2KkGBKE+FPXU1F0zKww+stnlUxUTlBvLCiWdP63Z9sqXYeNI/ziNzsxAp4LAdUcTQmXjw1IWgvm5CAb/BHy99w==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/otlp-exporter-base@0.56.0':
resolution: {integrity: sha512-eURvv0fcmBE+KE1McUeRo+u0n18ZnUeSc7lDlW/dzlqFYasEbsztTK4v0Qf8C4vEY+aMTjPKUxBG0NX2Te3Pmw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/otlp-transformer@0.56.0':
resolution: {integrity: sha512-kVkH/W2W7EpgWWpyU5VnnjIdSD7Y7FljQYObAQSKdRcejiwMj2glypZtUdfq1LTJcv4ht0jyTrw1D3CCxssNtQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/propagator-b3@1.30.1':
resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/propagator-jaeger@1.30.1':
resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/resources@1.29.0':
resolution: {integrity: sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/resources@1.30.1':
resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/sdk-logs@0.56.0':
resolution: {integrity: sha512-OS0WPBJF++R/cSl+terUjQH5PebloidB1Jbbecgg2rnCmQbTST9xsRes23bLfDQVRvmegmHqDh884h0aRdJyLw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.4.0 <1.10.0'
'@opentelemetry/sdk-metrics@1.29.0':
resolution: {integrity: sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
'@opentelemetry/sdk-trace-base@1.29.0':
resolution: {integrity: sha512-hEOpAYLKXF3wGJpXOtWsxEtqBgde0SCv+w+jvr3/UusR4ll3QrENEGnSl1WDCyRrpqOQ5NCNOvZch9UFVa7MnQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/sdk-trace-base@1.30.1':
resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/sdk-trace-node@1.30.1':
resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
'@opentelemetry/semantic-conventions@1.28.0':
resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==}
engines: {node: '>=14'}
'@opentelemetry/semantic-conventions@1.36.0':
resolution: {integrity: sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==}
engines: {node: '>=14'}
'@oxc-resolver/binding-darwin-arm64@11.2.0':
resolution: {integrity: sha512-ruKLkS+Dm/YIJaUhzEB7zPI+jh3EXxu0QnNV8I7t9jf0lpD2VnltuyRbhrbJEkksklZj//xCMyFFsILGjiU2Mg==}
cpu: [arm64]
@ -2352,6 +2485,36 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@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'}
@ -3903,6 +4066,9 @@ packages:
'@types/shell-quote@1.7.5':
resolution: {integrity: sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==}
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
'@types/stack-utils@2.0.3':
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
@ -4138,6 +4304,11 @@ packages:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies:
acorn: ^8
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -4589,6 +4760,9 @@ packages:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
cjs-module-lexer@1.4.3:
resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@ -6248,6 +6422,9 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
import-in-the-middle@1.14.2:
resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@ -7022,6 +7199,9 @@ 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==}
@ -7383,6 +7563,9 @@ packages:
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
module-details-from-path@1.0.4:
resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==}
monaco-vscode-textmate-theme-converter@0.1.7:
resolution: {integrity: sha512-ZMsq1RPWwOD3pvXD0n+9ddnhfzZoiUMwNIWPNUqYqEiQeH2HjyZ9KYOdt/pqe0kkN8WnYWLrxT9C/SrtIsAu2Q==}
hasBin: true
@ -8040,6 +8223,10 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
protobufjs@7.5.3:
resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==}
engines: {node: '>=12.0.0'}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@ -8350,6 +8537,10 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-in-the-middle@7.5.2:
resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==}
engines: {node: '>=8.6.0'}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
@ -8562,6 +8753,9 @@ packages:
shiki@3.4.1:
resolution: {integrity: sha512-PSnoczt+iWIOB4iRQ+XVPFtTuN1FcmuYzPgUBZTSv5pC6CozssIx2M4O5n4S9gJlUu9A3FxMU0ZPaHflky/6LA==}
shimmer@1.2.1:
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@ -11487,6 +11681,127 @@ snapshots:
'@open-draft/until@2.1.0': {}
'@opentelemetry/api-logs@0.56.0':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/api@1.9.0': {}
'@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core@1.29.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/exporter-trace-otlp-http@0.56.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-exporter-base': 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-transformer': 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/api-logs': 0.56.0
'@types/shimmer': 1.2.0
import-in-the-middle: 1.14.2
require-in-the-middle: 7.5.2
semver: 7.7.2
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/otlp-exporter-base@0.56.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-transformer': 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/otlp-transformer@0.56.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/api-logs': 0.56.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-logs': 0.56.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-metrics': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.29.0(@opentelemetry/api@1.9.0)
protobufjs: 7.5.3
'@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/resources@1.29.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/sdk-logs@0.56.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/api-logs': 0.56.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-metrics@1.29.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base@1.29.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.29.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
'@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
semver: 7.7.2
'@opentelemetry/semantic-conventions@1.28.0': {}
'@opentelemetry/semantic-conventions@1.36.0': {}
'@oxc-resolver/binding-darwin-arm64@11.2.0':
optional: true
@ -11536,6 +11851,29 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
'@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)
@ -13242,7 +13580,6 @@ snapshots:
'@types/node@20.19.9':
dependencies:
undici-types: 6.21.0
optional: true
'@types/node@22.15.29':
dependencies:
@ -13269,6 +13606,8 @@ snapshots:
'@types/shell-quote@1.7.5': {}
'@types/shimmer@1.2.0': {}
'@types/stack-utils@2.0.3': {}
'@types/stacktrace-js@2.0.3':
@ -13604,6 +13943,10 @@ snapshots:
mime-types: 3.0.1
negotiator: 1.0.0
acorn-import-attributes@1.9.5(acorn@8.15.0):
dependencies:
acorn: 8.15.0
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
acorn: 8.14.1
@ -14116,6 +14459,8 @@ snapshots:
ci-info@3.9.0: {}
cjs-module-lexer@1.4.3: {}
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@ -14723,9 +15068,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
drizzle-orm@0.44.1(@libsql/client@0.15.8)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
drizzle-orm@0.44.1(@libsql/client@0.15.8)(@opentelemetry/api@1.9.0)(better-sqlite3@11.10.0)(gel@2.1.0)(postgres@3.4.7):
optionalDependencies:
'@libsql/client': 0.15.8
'@opentelemetry/api': 1.9.0
better-sqlite3: 11.10.0
gel: 2.1.0
postgres: 3.4.7
@ -15999,6 +16345,13 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
import-in-the-middle@1.14.2:
dependencies:
acorn: 8.15.0
acorn-import-attributes: 1.9.5(acorn@8.15.0)
cjs-module-lexer: 1.4.3
module-details-from-path: 1.0.4
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
@ -16765,6 +17118,8 @@ snapshots:
strip-ansi: 7.1.0
wrap-ansi: 9.0.0
long@5.3.2: {}
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@ -17357,7 +17712,7 @@ snapshots:
mlly@1.7.4:
dependencies:
acorn: 8.14.1
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
@ -17385,6 +17740,8 @@ snapshots:
yargs-parser: 21.1.1
yargs-unparser: 2.0.0
module-details-from-path@1.0.4: {}
monaco-vscode-textmate-theme-converter@0.1.7(tslib@2.8.1):
dependencies:
commander: 8.3.0
@ -17442,7 +17799,7 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next@15.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next@15.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 15.2.5
'@swc/counter': 0.1.3
@ -17462,6 +17819,7 @@ snapshots:
'@next/swc-linux-x64-musl': 15.2.5
'@next/swc-win32-arm64-msvc': 15.2.5
'@next/swc-win32-x64-msvc': 15.2.5
'@opentelemetry/api': 1.9.0
sharp: 0.33.5
transitivePeerDependencies:
- '@babel/core'
@ -18058,6 +18416,21 @@ snapshots:
property-information@7.1.0: {}
protobufjs@7.5.3:
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': 20.19.9
long: 5.3.2
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@ -18507,6 +18880,14 @@ snapshots:
require-directory@2.1.1: {}
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.1(supports-color@8.1.1)
module-details-from-path: 1.0.4
resolve: 1.22.10
transitivePeerDependencies:
- supports-color
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
@ -18784,6 +19165,8 @@ snapshots:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
shimmer@1.2.1: {}
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0

View file

@ -2611,5 +2611,13 @@ export const webviewMessageHandler = async (
}
break
}
case "otelEnabled":
await updateGlobalState("otelEnabled", message.bool ?? false)
await provider.postStateToWebview()
break
case "otelEndpoints":
await updateGlobalState("otelEndpoints", message.endpoints ?? [])
await provider.postStateToWebview()
break
}
}

View file

@ -13,7 +13,7 @@ try {
}
import { CloudService, UnifiedBridgeService } from "@roo-code/cloud"
import { TelemetryService, PostHogTelemetryClient } from "@roo-code/telemetry"
import { TelemetryService, PostHogTelemetryClient, OpenTelemetryClient } from "@roo-code/telemetry"
import "./utils/path" // Necessary to have access to String.prototype.toPosix.
import { createOutputChannelLogger, createDualLogger } from "./utils/outputChannelLogger"
@ -94,6 +94,26 @@ export async function activate(context: vscode.ExtensionContext) {
const contextProxy = await ContextProxy.getInstance(context)
// Initialize OpenTelemetry client if enabled
const otelClient = new OpenTelemetryClient()
try {
const otelEnabled = contextProxy.getValue("otelEnabled") ?? false
const otelEndpoints = contextProxy.getValue("otelEndpoints") ?? []
if (otelEnabled && otelEndpoints.length > 0) {
await otelClient.initialize(otelEndpoints)
telemetryService.register(otelClient)
outputChannel.appendLine(
`[OpenTelemetry] Initialized with ${otelEndpoints.filter((e) => e.enabled).length} endpoints`,
)
}
} catch (error) {
console.warn("Failed to initialize OpenTelemetryClient:", error)
outputChannel.appendLine(
`[OpenTelemetry] Failed to initialize: ${error instanceof Error ? error.message : String(error)}`,
)
}
// Initialize code index managers for all workspace folders
const codeIndexManagers: CodeIndexManager[] = []
if (vscode.workspace.workspaceFolders) {

View file

@ -121,6 +121,8 @@ export interface ExtensionMessage {
| "showEditMessageDialog"
| "commands"
| "insertTextIntoTextarea"
| "otelEnabled"
| "otelEndpoints"
text?: string
payload?: any // Add a generic payload for now, can refine later
action?:
@ -195,6 +197,8 @@ export interface ExtensionMessage {
messageTs?: number
context?: string
commands?: Command[]
bool?: boolean
endpoints?: Array<{ url: string; headers?: Record<string, string>; enabled: boolean }>
}
export type ExtensionState = Pick<
@ -272,6 +276,8 @@ export type ExtensionState = Pick<
| "includeDiagnosticMessages"
| "maxDiagnosticMessages"
| "remoteControlEnabled"
| "otelEnabled"
| "otelEndpoints"
> & {
version: string
clineMessages: ClineMessage[]

View file

@ -211,6 +211,8 @@ export interface WebviewMessage {
| "deleteCommand"
| "createCommand"
| "insertTextIntoTextarea"
| "otelEnabled"
| "otelEndpoints"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
@ -254,6 +256,7 @@ export interface WebviewMessage {
visibility?: ShareVisibility // For share visibility
hasContent?: boolean // For checkRulesDirectoryResult
checkOnly?: boolean // For deleteCustomMode check
endpoints?: Array<{ url: string; headers?: Record<string, string>; enabled: boolean }> // For otelEndpoints
codeIndexSettings?: {
// Global state settings
codebaseIndexEnabled: boolean

View file

@ -0,0 +1,284 @@
import { HTMLAttributes, useState } from "react"
import { VSCodeCheckbox, VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { Activity, Plus, Trash2, ChevronDown, ChevronRight } from "lucide-react"
import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
interface OtelEndpoint {
url: string
headers?: Record<string, string>
enabled: boolean
}
type OpenTelemetrySettingsProps = HTMLAttributes<HTMLDivElement> & {
otelEnabled?: boolean
otelEndpoints?: OtelEndpoint[]
setCachedStateField: SetCachedStateField<"otelEnabled" | "otelEndpoints">
}
export const OpenTelemetrySettings = ({
otelEnabled,
otelEndpoints = [],
setCachedStateField,
...props
}: OpenTelemetrySettingsProps) => {
const [expandedEndpoints, setExpandedEndpoints] = useState<Set<number>>(new Set())
const handleAddEndpoint = () => {
const newEndpoint: OtelEndpoint = {
url: "",
headers: {},
enabled: true,
}
setCachedStateField("otelEndpoints", [...otelEndpoints, newEndpoint])
// Auto-expand the new endpoint
setExpandedEndpoints(new Set(Array.from(expandedEndpoints).concat(otelEndpoints.length)))
}
const handleRemoveEndpoint = (index: number) => {
const updated = otelEndpoints.filter((_, i) => i !== index)
setCachedStateField("otelEndpoints", updated)
// Remove from expanded set
const newExpanded = new Set(expandedEndpoints)
newExpanded.delete(index)
setExpandedEndpoints(newExpanded)
}
const handleUpdateEndpoint = (index: number, field: keyof OtelEndpoint, value: any) => {
const updated = [...otelEndpoints]
updated[index] = { ...updated[index], [field]: value }
setCachedStateField("otelEndpoints", updated)
}
const handleAddHeader = (endpointIndex: number, key: string, value: string) => {
if (!key) return
const updated = [...otelEndpoints]
updated[endpointIndex] = {
...updated[endpointIndex],
headers: { ...updated[endpointIndex].headers, [key]: value },
}
setCachedStateField("otelEndpoints", updated)
}
const handleRemoveHeader = (endpointIndex: number, key: string) => {
const updated = [...otelEndpoints]
const headers = { ...updated[endpointIndex].headers }
delete headers[key]
updated[endpointIndex] = { ...updated[endpointIndex], headers }
setCachedStateField("otelEndpoints", updated)
}
const toggleExpanded = (index: number) => {
const newExpanded = new Set(expandedEndpoints)
if (newExpanded.has(index)) {
newExpanded.delete(index)
} else {
newExpanded.add(index)
}
setExpandedEndpoints(newExpanded)
}
return (
<div {...props}>
<SectionHeader>
<div className="flex items-center gap-2">
<Activity className="w-4" />
<div>OpenTelemetry</div>
</div>
</SectionHeader>
<Section>
<div>
<VSCodeCheckbox
checked={otelEnabled}
onChange={(e: any) => setCachedStateField("otelEnabled", e.target.checked)}
data-testid="otel-enabled-checkbox">
<span className="font-medium">Enable OpenTelemetry</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
Send telemetry traces to custom OpenTelemetry collector endpoints
</div>
</div>
{otelEnabled && (
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
<div className="flex justify-between items-center">
<label className="font-medium">Collector Endpoints</label>
<VSCodeButton
appearance="secondary"
onClick={handleAddEndpoint}
data-testid="add-endpoint-button">
<Plus className="w-4 h-4 mr-1" />
Add Endpoint
</VSCodeButton>
</div>
{otelEndpoints.length === 0 ? (
<div className="text-vscode-descriptionForeground text-sm">
No endpoints configured. Click &quot;Add Endpoint&quot; to add a custom OTEL collector.
</div>
) : (
<div className="flex flex-col gap-3">
{otelEndpoints.map((endpoint, index) => {
const isExpanded = expandedEndpoints.has(index)
return (
<div key={index} className="border border-vscode-panel-border rounded p-3">
<div className="flex items-start gap-2">
<button
onClick={() => toggleExpanded(index)}
className="p-0 bg-transparent border-0 cursor-pointer text-vscode-foreground hover:text-vscode-focusBorder"
aria-label={isExpanded ? "Collapse" : "Expand"}>
{isExpanded ? (
<ChevronDown className="w-4 h-4" />
) : (
<ChevronRight className="w-4 h-4" />
)}
</button>
<div className="flex-1">
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={endpoint.enabled}
onChange={(e: any) =>
handleUpdateEndpoint(index, "enabled", e.target.checked)
}
data-testid={`endpoint-${index}-enabled`}>
<span className="font-medium">Endpoint {index + 1}</span>
</VSCodeCheckbox>
<button
onClick={() => handleRemoveEndpoint(index)}
className="ml-auto p-1 bg-transparent border-0 cursor-pointer text-vscode-errorForeground hover:text-vscode-errorForeground"
aria-label="Remove endpoint"
data-testid={`remove-endpoint-${index}`}>
<Trash2 className="w-4 h-4" />
</button>
</div>
{isExpanded && (
<div className="mt-3 flex flex-col gap-3">
<div>
<label className="block text-sm font-medium mb-1">
URL
</label>
<VSCodeTextField
value={endpoint.url}
placeholder="https://otel-collector.example.com/v1/traces"
onInput={(e: any) =>
handleUpdateEndpoint(
index,
"url",
e.target.value,
)
}
className="w-full"
data-testid={`endpoint-${index}-url`}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
Headers (Optional)
</label>
<div className="flex flex-col gap-2">
{Object.entries(endpoint.headers || {}).map(
([key, value]) => (
<div
key={key}
className="flex items-center gap-2">
<VSCodeTextField
value={key}
placeholder="Header name"
className="flex-1"
disabled
/>
<VSCodeTextField
value={value}
placeholder="Header value"
className="flex-1"
onInput={(e: any) => {
const newHeaders = {
...endpoint.headers,
}
delete newHeaders[key]
newHeaders[key] = e.target.value
handleUpdateEndpoint(
index,
"headers",
newHeaders,
)
}}
/>
<button
onClick={() =>
handleRemoveHeader(index, key)
}
className="p-1 bg-transparent border-0 cursor-pointer text-vscode-errorForeground hover:text-vscode-errorForeground"
aria-label="Remove header">
<Trash2 className="w-3 h-3" />
</button>
</div>
),
)}
<div className="flex items-center gap-2">
<VSCodeTextField
placeholder="Header name (e.g., Authorization)"
className="flex-1"
id={`new-header-key-${index}`}
/>
<VSCodeTextField
placeholder="Header value"
className="flex-1"
id={`new-header-value-${index}`}
/>
<VSCodeButton
appearance="secondary"
onClick={() => {
const keyInput =
document.getElementById(
`new-header-key-${index}`,
) as HTMLInputElement
const valueInput =
document.getElementById(
`new-header-value-${index}`,
) as HTMLInputElement
if (
keyInput &&
valueInput &&
keyInput.value
) {
handleAddHeader(
index,
keyInput.value,
valueInput.value,
)
keyInput.value = ""
valueInput.value = ""
}
}}>
Add
</VSCodeButton>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
)
})}
</div>
)}
<div className="text-vscode-descriptionForeground text-sm mt-2">
<strong>Note:</strong> Changes to endpoints require restarting VS Code to take effect.
</div>
</div>
)}
</Section>
</div>
)
}

View file

@ -23,6 +23,7 @@ import {
Info,
MessageSquare,
LucideIcon,
Activity,
} from "lucide-react"
import type { ProviderSettings, ExperimentId } from "@roo-code/types"
@ -65,6 +66,7 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
import { OpenTelemetrySettings } from "./OpenTelemetrySettings"
import { cn } from "@/lib/utils"
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
@ -87,6 +89,7 @@ const sectionNames = [
"contextManagement",
"terminal",
"prompts",
"opentelemetry",
"experimental",
"language",
"about",
@ -183,6 +186,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
includeDiagnosticMessages,
maxDiagnosticMessages,
includeTaskHistoryInEnhance,
otelEnabled,
otelEndpoints,
} = cachedState
const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration])
@ -345,6 +350,8 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
vscode.postMessage({ type: "otelEnabled", bool: otelEnabled ?? false })
vscode.postMessage({ type: "otelEndpoints", endpoints: otelEndpoints ?? [] })
setChangeDetected(false)
}
}
@ -422,6 +429,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{ id: "contextManagement", icon: Database },
{ id: "terminal", icon: SquareTerminal },
{ id: "prompts", icon: MessageSquare },
{ id: "opentelemetry", icon: Activity },
{ id: "experimental", icon: FlaskConical },
{ id: "language", icon: Globe },
{ id: "about", icon: Info },
@ -716,6 +724,15 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
/>
)}
{/* OpenTelemetry Section */}
{activeTab === "opentelemetry" && (
<OpenTelemetrySettings
otelEnabled={otelEnabled}
otelEndpoints={otelEndpoints}
setCachedStateField={setCachedStateField}
/>
)}
{/* Experimental Section */}
{activeTab === "experimental" && (
<ExperimentalSettings setExperimentEnabled={setExperimentEnabled} experiments={experiments} />

View file

@ -149,6 +149,10 @@ export interface ExtensionStateContextType extends ExtensionState {
setMaxDiagnosticMessages: (value: number) => void
includeTaskHistoryInEnhance?: boolean
setIncludeTaskHistoryInEnhance: (value: boolean) => void
otelEnabled?: boolean
setOtelEnabled: (value: boolean) => void
otelEndpoints?: Array<{ url: string; headers?: Record<string, string>; enabled: boolean }>
setOtelEndpoints: (value: Array<{ url: string; headers?: Record<string, string>; enabled: boolean }>) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -269,6 +273,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
global: {},
})
const [includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance] = useState(false)
const [otelEnabled, setOtelEnabled] = useState(false)
const [otelEndpoints, setOtelEndpoints] = useState<
Array<{ url: string; headers?: Record<string, string>; enabled: boolean }>
>([])
const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@ -306,6 +314,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
if ((newState as any).includeTaskHistoryInEnhance !== undefined) {
setIncludeTaskHistoryInEnhance((newState as any).includeTaskHistoryInEnhance)
}
// Update OTEL settings if present in state message
if ((newState as any).otelEnabled !== undefined) {
setOtelEnabled((newState as any).otelEnabled)
}
if ((newState as any).otelEndpoints !== undefined) {
setOtelEndpoints((newState as any).otelEndpoints)
}
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
@ -522,6 +537,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
},
includeTaskHistoryInEnhance,
setIncludeTaskHistoryInEnhance,
otelEnabled,
setOtelEnabled,
otelEndpoints,
setOtelEndpoints,
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>