mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
245bc2a2e6
commit
bb00d6c114
13 changed files with 1467 additions and 8 deletions
|
|
@ -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"
|
||||
|
|
|
|||
209
packages/telemetry/src/OpenTelemetryClient.ts
Normal file
209
packages/telemetry/src/OpenTelemetryClient.ts
Normal 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]
|
||||
}
|
||||
}
|
||||
490
packages/telemetry/src/__tests__/OpenTelemetryClient.test.ts
Normal file
490
packages/telemetry/src/__tests__/OpenTelemetryClient.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./BaseTelemetryClient"
|
||||
export * from "./PostHogTelemetryClient"
|
||||
export * from "./OpenTelemetryClient"
|
||||
export * from "./TelemetryService"
|
||||
|
|
|
|||
|
|
@ -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
397
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
284
webview-ui/src/components/settings/OpenTelemetrySettings.tsx
Normal file
284
webview-ui/src/components/settings/OpenTelemetrySettings.tsx
Normal 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 "Add Endpoint" 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue