diff --git a/docs/logging.md b/docs/logging.md index 69cf5d704..2159d877e 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -136,6 +136,72 @@ Tool summaries can redact sensitive tokens before they hit the console: Redaction affects **console output only** and does not alter file logs. +## Diagnostics + OpenTelemetry + +Diagnostics are **opt-in** structured events for model runs (usage + cost + +context + duration). They do **not** replace logs; they exist to feed metrics, +traces, and other exporters. + +Clawdbot currently emits a `model.usage` event after each agent run with: + +- Token counts (input/output/cache/prompt/total) +- Estimated cost (USD) +- Context window used/limit +- Duration (ms) +- Provider/channel/model + session identifiers + +### Enable diagnostics (no exporter) + +Use this if you want diagnostics events available to plugins or custom sinks: + +```json +{ + "diagnostics": { + "enabled": true + } +} +``` + +### Export to OpenTelemetry + +Diagnostics can be exported via the `diagnostics-otel` plugin (OTLP/HTTP). This +works with any OpenTelemetry collector/backend that accepts OTLP/HTTP. + +```json +{ + "plugins": { + "allow": ["diagnostics-otel"], + "entries": { + "diagnostics-otel": { + "enabled": true + } + } + }, + "diagnostics": { + "enabled": true, + "otel": { + "enabled": true, + "endpoint": "http://otel-collector:4318", + "protocol": "http/protobuf", + "serviceName": "clawdbot-gateway", + "traces": true, + "metrics": true, + "sampleRate": 0.2, + "flushIntervalMs": 60000 + } + } +} +``` + +Notes: +- You can also enable the plugin with `clawdbot plugins enable diagnostics-otel`. +- `protocol` currently supports `http/protobuf`. +- Metrics include token usage, cost, context size, and run duration. +- Traces/metrics can be toggled with `traces` / `metrics` (default: on). +- Set `headers` when your collector requires auth. +- Environment variables supported: `OTEL_EXPORTER_OTLP_ENDPOINT`, + `OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_PROTOCOL`. + ## Troubleshooting tips - **Gateway not reachable?** Run `clawdbot doctor` first. diff --git a/extensions/diagnostics-otel/clawdbot.plugin.json b/extensions/diagnostics-otel/clawdbot.plugin.json new file mode 100644 index 000000000..3207d1f18 --- /dev/null +++ b/extensions/diagnostics-otel/clawdbot.plugin.json @@ -0,0 +1,8 @@ +{ + "id": "diagnostics-otel", + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/extensions/diagnostics-otel/index.ts b/extensions/diagnostics-otel/index.ts new file mode 100644 index 000000000..28772051f --- /dev/null +++ b/extensions/diagnostics-otel/index.ts @@ -0,0 +1,16 @@ +import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk"; +import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk"; + +import { createDiagnosticsOtelService } from "./src/service.js"; + +const plugin = { + id: "diagnostics-otel", + name: "Diagnostics OpenTelemetry", + description: "Export diagnostics events to OpenTelemetry", + configSchema: emptyPluginConfigSchema(), + register(api: ClawdbotPluginApi) { + api.registerService(createDiagnosticsOtelService()); + }, +}; + +export default plugin; diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json new file mode 100644 index 000000000..8c43def2c --- /dev/null +++ b/extensions/diagnostics-otel/package.json @@ -0,0 +1,19 @@ +{ + "name": "@clawdbot/diagnostics-otel", + "version": "2026.1.17-1", + "type": "module", + "description": "Clawdbot diagnostics OpenTelemetry exporter", + "clawdbot": { + "extensions": ["./index.ts"] + }, + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-metrics-otlp-http": "^0.210.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-metrics": "^2.4.0", + "@opentelemetry/sdk-node": "^0.210.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + } +} diff --git a/extensions/diagnostics-otel/src/service.ts b/extensions/diagnostics-otel/src/service.ts new file mode 100644 index 000000000..9d4e59e72 --- /dev/null +++ b/extensions/diagnostics-otel/src/service.ts @@ -0,0 +1,196 @@ +import { metrics, trace } from "@opentelemetry/api"; +import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"; +import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { Resource } from "@opentelemetry/resources"; +import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"; +import { NodeSDK } from "@opentelemetry/sdk-node"; +import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base"; +import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; + +import type { + ClawdbotPluginService, + DiagnosticUsageEvent, +} from "clawdbot/plugin-sdk"; +import { onDiagnosticEvent } from "clawdbot/plugin-sdk"; + +const DEFAULT_SERVICE_NAME = "clawdbot"; + +function normalizeEndpoint(endpoint?: string): string | undefined { + const trimmed = endpoint?.trim(); + return trimmed ? trimmed.replace(/\/+$/, "") : undefined; +} + +function resolveOtelUrl(endpoint: string | undefined, path: string): string | undefined { + if (!endpoint) return undefined; + if (endpoint.includes("/v1/")) return endpoint; + return `${endpoint}/${path}`; +} + +function resolveSampleRate(value: number | undefined): number | undefined { + if (typeof value !== "number" || !Number.isFinite(value)) return undefined; + if (value < 0 || value > 1) return undefined; + return value; +} + +export function createDiagnosticsOtelService(): ClawdbotPluginService { + let sdk: NodeSDK | null = null; + let unsubscribe: (() => void) | null = null; + + return { + id: "diagnostics-otel", + async start(ctx) { + const cfg = ctx.config.diagnostics; + const otel = cfg?.otel; + if (!cfg?.enabled || !otel?.enabled) return; + + const protocol = otel.protocol ?? process.env.OTEL_EXPORTER_OTLP_PROTOCOL ?? "http/protobuf"; + if (protocol !== "http/protobuf") { + ctx.logger.warn(`diagnostics-otel: unsupported protocol ${protocol}`); + return; + } + + const endpoint = normalizeEndpoint(otel.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT); + const headers = otel.headers ?? undefined; + const serviceName = + otel.serviceName?.trim() || process.env.OTEL_SERVICE_NAME || DEFAULT_SERVICE_NAME; + const sampleRate = resolveSampleRate(otel.sampleRate); + + const tracesEnabled = otel.traces !== false; + const metricsEnabled = otel.metrics !== false; + if (!tracesEnabled && !metricsEnabled) return; + + const resource = new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: serviceName, + }); + + const traceUrl = resolveOtelUrl(endpoint, "v1/traces"); + const metricUrl = resolveOtelUrl(endpoint, "v1/metrics"); + const traceExporter = tracesEnabled + ? new OTLPTraceExporter({ + ...(traceUrl ? { url: traceUrl } : {}), + ...(headers ? { headers } : {}), + }) + : undefined; + + const metricExporter = metricsEnabled + ? new OTLPMetricExporter({ + ...(metricUrl ? { url: metricUrl } : {}), + ...(headers ? { headers } : {}), + }) + : undefined; + + const metricReader = metricExporter + ? new PeriodicExportingMetricReader({ + exporter: metricExporter, + ...(typeof otel.flushIntervalMs === "number" + ? { exportIntervalMillis: Math.max(1000, otel.flushIntervalMs) } + : {}), + }) + : undefined; + + sdk = new NodeSDK({ + resource, + ...(traceExporter ? { traceExporter } : {}), + ...(metricReader ? { metricReader } : {}), + ...(sampleRate !== undefined + ? { + sampler: new ParentBasedSampler({ + root: new TraceIdRatioBasedSampler(sampleRate), + }), + } + : {}), + }); + + await sdk.start(); + + const meter = metrics.getMeter("clawdbot"); + const tracer = trace.getTracer("clawdbot"); + + const tokensCounter = meter.createCounter("clawdbot.tokens", { + unit: "1", + description: "Token usage by type", + }); + const costCounter = meter.createCounter("clawdbot.cost.usd", { + unit: "1", + description: "Estimated model cost (USD)", + }); + const durationHistogram = meter.createHistogram("clawdbot.run.duration_ms", { + unit: "ms", + description: "Agent run duration", + }); + const contextHistogram = meter.createHistogram("clawdbot.context.tokens", { + unit: "1", + description: "Context window size and usage", + }); + + unsubscribe = onDiagnosticEvent((evt) => { + if (evt.type !== "model.usage") return; + const usageEvent = evt as DiagnosticUsageEvent; + const attrs = { + "clawdbot.channel": usageEvent.channel ?? "unknown", + "clawdbot.provider": usageEvent.provider ?? "unknown", + "clawdbot.model": usageEvent.model ?? "unknown", + }; + + const usage = usageEvent.usage; + if (usage.input) tokensCounter.add(usage.input, { ...attrs, "clawdbot.token": "input" }); + if (usage.output) + tokensCounter.add(usage.output, { ...attrs, "clawdbot.token": "output" }); + if (usage.cacheRead) + tokensCounter.add(usage.cacheRead, { ...attrs, "clawdbot.token": "cache_read" }); + if (usage.cacheWrite) + tokensCounter.add(usage.cacheWrite, { ...attrs, "clawdbot.token": "cache_write" }); + if (usage.promptTokens) + tokensCounter.add(usage.promptTokens, { ...attrs, "clawdbot.token": "prompt" }); + if (usage.total) + tokensCounter.add(usage.total, { ...attrs, "clawdbot.token": "total" }); + + if (usageEvent.costUsd) costCounter.add(usageEvent.costUsd, attrs); + if (usageEvent.durationMs) durationHistogram.record(usageEvent.durationMs, attrs); + if (usageEvent.context?.limit) + contextHistogram.record(usageEvent.context.limit, { + ...attrs, + "clawdbot.context": "limit", + }); + if (usageEvent.context?.used) + contextHistogram.record(usageEvent.context.used, { + ...attrs, + "clawdbot.context": "used", + }); + + if (!tracesEnabled) return; + const spanAttrs: Record = { + ...attrs, + "clawdbot.sessionKey": usageEvent.sessionKey ?? "", + "clawdbot.sessionId": usageEvent.sessionId ?? "", + "clawdbot.tokens.input": usage.input ?? 0, + "clawdbot.tokens.output": usage.output ?? 0, + "clawdbot.tokens.cache_read": usage.cacheRead ?? 0, + "clawdbot.tokens.cache_write": usage.cacheWrite ?? 0, + "clawdbot.tokens.total": usage.total ?? 0, + }; + + const startTime = usageEvent.durationMs + ? Date.now() - Math.max(0, usageEvent.durationMs) + : undefined; + const span = tracer.startSpan("clawdbot.model.usage", { + attributes: spanAttrs, + ...(startTime ? { startTime } : {}), + }); + span.end(); + }); + + if (otel.logs) { + ctx.logger.warn("diagnostics-otel: logs exporter not wired yet"); + } + }, + async stop() { + unsubscribe?.(); + unsubscribe = null; + if (sdk) { + await sdk.shutdown().catch(() => undefined); + sdk = null; + } + }, + } satisfies ClawdbotPluginService; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c17a6a5d..8a5589a80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,7 +243,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) wireit: specifier: ^0.14.12 version: 0.14.12 @@ -259,6 +259,33 @@ importers: extensions/copilot-proxy: {} + extensions/diagnostics-otel: + dependencies: + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/exporter-metrics-otlp-http': + specifier: ^0.210.0 + version: 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.210.0 + version: 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^2.4.0 + version: 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': + specifier: ^2.4.0 + version: 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': + specifier: ^0.210.0 + version: 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: ^2.4.0 + version: 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.39.0 + version: 1.39.0 + extensions/discord: {} extensions/google-antigravity-auth: {} @@ -393,7 +420,7 @@ importers: version: 5.9.3 vitest: specifier: 4.0.17 - version: 4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) packages: @@ -817,6 +844,15 @@ packages: '@grammyjs/types@3.23.0': resolution: {integrity: sha512-D3jQ4UWERPsyR3op/YFudMMIPNTU47vy7L51uO9/73tMELmjO/+LX5N36/Y0CG5IQfIsz43MxiHI5rgsK0/k+g==} + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + '@hapi/boom@9.1.4': resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==} @@ -1000,6 +1036,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@keyv/bigmap@1.3.0': resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} engines: {node: '>= 18'} @@ -1494,6 +1533,174 @@ packages: resolution: {integrity: sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==} engines: {node: '>= 20'} + '@opentelemetry/api-logs@0.210.0': + resolution: {integrity: sha512-CMtLxp+lYDriveZejpBND/2TmadrrhUfChyxzmkFtHaMDdSKfP59MAYyA0ICBvEBdm3iXwLcaj/8Ic/pnGw9Yg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/configuration@0.210.0': + resolution: {integrity: sha512-tM0ROS/hZM72kB55cSjDcghVcUXBJdGkGzpkhD7M1B/gpcvZPSGfjFgKN3dgmxNgF76NxtbUwv3ik0wS+Kz52g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.4.0': + resolution: {integrity: sha512-jn0phJ+hU7ZuvaoZE/8/Euw3gvHJrn2yi+kXrymwObEPVPjtwCmkvXDRQCWli+fCTTF/aSOtXaLr7CLIvv3LQg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.4.0': + resolution: {integrity: sha512-KtcyFHssTn5ZgDu6SXmUznS80OFs/wN7y6MyFRRcKU6TOw8hNcGxKvt8hsdaLJfhzUszNSjURetq5Qpkad14Gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.210.0': + resolution: {integrity: sha512-+BolenqOO6ow65go7uWRYPvvs/BBIWp1mtRn93VvGduqvMVH/IY8nXrt80a4L9hZ7lHi2Tq2/NcC3H2QzcWKag==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.210.0': + resolution: {integrity: sha512-Q8/SEQtgrErbVVRg9M9iaG8m5wdPNdU0UOF7U43sAhwfmPG92ZOk/aenKhg0DXSNJHhkCDNCgS1kSoErAB3z0A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.210.0': + resolution: {integrity: sha512-Y/yPc+gDhsWB7AsNzQWxblw4ULbvhCycMaQ2aAn+HSAVbgbMiZa0SbclPVHSnpnNzKSLVavFjweAr0pQA1KKLg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.210.0': + resolution: {integrity: sha512-pWZ/Tjrqev9rdkqe8F6A9FGddLZrjl6iRAU5LBvvRL6I3PSgG8z1xM0cESAy1jzAF4wGohnAh8rB7hHzpUOYEA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.210.0': + resolution: {integrity: sha512-JpLThG8Hh8A/Jzdzw9i4Ftu+EzvLaX/LouN+mOOHmadL0iror0Qsi3QWzucXeiUsDDsiYgjfKyi09e6sltytgA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.210.0': + resolution: {integrity: sha512-CFa7SOinYOVWIWJuQL7XFeyedzmFGIpHpSMNFE8Xefb6iGB4m+MukQecdssvPcJKYlfF5FpovEOLXwafAzsXWQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.210.0': + resolution: {integrity: sha512-8i+7d70Hho6pcheTtbqIuS+bo+AIX/oNUTMwIEZoehUE4ZdbGmeVaE+hJS2LAErFeFaU71w164lAgYyMUEQ8zw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.210.0': + resolution: {integrity: sha512-1GPLOyxIfUX24WM8Oea+vx9d9TlewposUnsQXTjusxVMQ/dWvt5JIDJyTsfNDS412XRUOORgF97PwsfDY5QKGA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.210.0': + resolution: {integrity: sha512-9JkyaCl70anEtuKZdoCQmjDuz1/paEixY/DWfsvHt7PGKq3t8/nQ/6/xwxHjG+SkPAUbo1Iq4h7STe7Pk2bc5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.210.0': + resolution: {integrity: sha512-qVUY7Hsm/t5buGOtPcTV1Ch4W9kj2wGaQaAF5FO4XR8TMKl2GM45tUCnr0/1dF3wo4RG9khMxrddeQWdRL4fIg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.4.0': + resolution: {integrity: sha512-qpiXY0TUEFjBBp9b1na9LfuVQw6W8LH+te7uv+CC+0Up78ZDtZZwOjK2M7CL7Nspnw+yS4JdgEA7oxsBu0Ctsg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation@0.210.0': + resolution: {integrity: sha512-sLMhyHmW9katVaLUOKpfCnxSGhZq2t1ReWgwsu2cSgxmDVMB690H9TanuexanpFI94PJaokrqbp8u9KYZDUT5g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.210.0': + resolution: {integrity: sha512-uk78DcZoBNHIm26h0oXc8Pizh4KDJ/y04N5k/UaI9J7xR7mL8QcMcYPQG9xxN7m8qotXOMDRW6qTAyptav4+3w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.210.0': + resolution: {integrity: sha512-fEJs8UhkFMrdXMOCLXyKd2uc6N209tIi8IBNqSTi83ri+MlMFrBKnOtklmv9/zzxovoN5zD1waRt6XBFGPfmIw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.210.0': + resolution: {integrity: sha512-nkHBJVSJGOwkRZl+BFIr7gikA93/U8XkL2EWaiDbj3DVjmTEZQpegIKk0lT8oqQYfP8FC6zWNjuTfkaBVqa0ZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.4.0': + resolution: {integrity: sha512-6VPsFiMUkJBre/86F0d+PZMaUCcuLA9DtZuC46KH8EeVEKZPEM2WlX35M/qmde8UpzoQL9qzdz54YjUYABt8Uw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.4.0': + resolution: {integrity: sha512-t6muBL/3AMD++1EMF658C/KIpj3gfmTmftX3mEQql4KIxNGFvacCmmTtrQt9IZAJmQRfjQRCkv+vsGbQugeJIw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@2.4.0': + resolution: {integrity: sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.210.0': + resolution: {integrity: sha512-YuaL92Dpyk/Kc1o4e9XiaWWwiC0aBFN+4oy+6A9TP4UNJmRymPMEX10r6EMMFMD7V0hktiSig9cwWo59peeLCQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.4.0': + resolution: {integrity: sha512-qSbfq9mXbLMqmPEjijl32f3ZEmiHekebRggPdPjhHI6t1CsAQOR2Aw/SuTDftk3/l2aaPHpwP3xM2DkgBA1ANw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.210.0': + resolution: {integrity: sha512-KymqUtYvfpblDNgGxBXYqCcDjYXwjOF7Muc6ocs0rMlG/66Hcs9KiJ7hg4zLOv63JubF/vxi5WXaLrQrPKyaZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.4.0': + resolution: {integrity: sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.4.0': + resolution: {integrity: sha512-MBc2l04hZPYygnWPT38UiOPy9ueutPqmJ47z0m9IKuoVQh3MblmbSgwspjhdHagZLfSfmlzhWR1xtbgVNmjX2A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.39.0': + resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} + engines: {node: '>=14'} + '@oxc-project/types@0.108.0': resolution: {integrity: sha512-7lf13b2IA/kZO6xgnIZA88sq3vwrxWk+2vxf6cc+omwYCRTiA5e63Beqf3fz/v8jEviChWWmFYBwzfSeyrsj7Q==} @@ -2376,6 +2583,16 @@ 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@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -2633,6 +2850,9 @@ packages: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -3269,6 +3489,9 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-in-the-middle@2.0.4: + resolution: {integrity: sha512-Al0kMpa0BqfvDnxjxGlab9vdQ0vTDs82TBKrD59X9jReUoPAzSGBb6vGDzMUMFBGyyDF03RpLT4oxGn6BpASzQ==} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3759,6 +3982,9 @@ packages: engines: {node: '>=10'} hasBin: true + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + morgan@1.10.1: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} @@ -4161,6 +4387,10 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} + protobufjs@8.0.0: + resolution: {integrity: sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4275,6 +4505,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -5632,6 +5866,18 @@ snapshots: '@grammyjs/types@3.23.0': {} + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + '@hapi/boom@9.1.4': dependencies: '@hapi/hoek': 9.3.0 @@ -5779,6 +6025,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@js-sdsl/ordered-map@4.4.2': {} + '@keyv/bigmap@1.3.0(keyv@5.5.5)': dependencies: hashery: 1.4.0 @@ -6333,6 +6581,241 @@ snapshots: '@octokit/webhooks-methods': 6.0.0 optional: true + '@opentelemetry/api-logs@0.210.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/configuration@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + yaml: 2.8.2 + + '@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.210.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-http@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.210.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/instrumentation@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + import-in-the-middle: 2.0.4 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.210.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + protobufjs: 8.0.0 + + '@opentelemetry/propagator-b3@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-logs@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.210.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.210.0 + '@opentelemetry/configuration': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.210.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-node@2.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.4.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/semantic-conventions@1.39.0': {} + '@oxc-project/types@0.108.0': {} '@oxfmt/darwin-arm64@0.26.0': @@ -7145,7 +7628,7 @@ snapshots: '@vitest/mocker': 4.0.17(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) playwright: 1.57.0 tinyrainbow: 3.0.3 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - bufferutil - msw @@ -7161,7 +7644,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) ws: 8.19.0 transitivePeerDependencies: - bufferutil @@ -7181,7 +7664,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vitest: 4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: '@vitest/browser': 4.0.17(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.17) @@ -7290,6 +7773,12 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + agent-base@7.1.4: {} ajv-formats@3.0.1(ajv@8.17.1): @@ -7575,6 +8064,8 @@ snapshots: ci-info@4.3.1: optional: true + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -7607,7 +8098,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - optional: true clsx@2.1.1: {} @@ -8323,6 +8813,13 @@ snapshots: immediate@3.0.6: {} + import-in-the-middle@2.0.4: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + inherits@2.0.4: {} ini@1.3.8: @@ -8804,6 +9301,8 @@ snapshots: mkdirp@3.0.1: {} + module-details-from-path@1.0.4: {} + morgan@1.10.1: dependencies: basic-auth: 2.0.1 @@ -9255,6 +9754,21 @@ snapshots: '@types/node': 25.0.9 long: 5.3.2 + protobufjs@8.0.0: + 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': 25.0.9 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -9409,6 +9923,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resolve-pkg-maps@1.0.0: {} restore-cursor@5.1.0: @@ -9998,7 +10519,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vitest@4.0.17(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.9)(@vitest/browser-playwright@4.0.17)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.17 '@vitest/mocker': 4.0.17(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) @@ -10021,6 +10542,7 @@ snapshots: vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 25.0.9 '@vitest/browser-playwright': 4.0.17(playwright@1.57.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.17) transitivePeerDependencies: @@ -10109,8 +10631,7 @@ snapshots: yargs-parser@20.2.9: {} - yargs-parser@21.1.1: - optional: true + yargs-parser@21.1.1: {} yargs@16.2.0: dependencies: @@ -10131,7 +10652,6 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - optional: true yoctocolors@2.1.2: {} diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index 4e13ebdf5..da73bf9de 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -18,7 +18,7 @@ import { import type { TypingMode } from "../../config/types.js"; import { logVerbose } from "../../globals.js"; import { defaultRuntime } from "../../runtime.js"; -import { resolveModelCostConfig } from "../../utils/usage-format.js"; +import { estimateUsageCost, resolveModelCostConfig } from "../../utils/usage-format.js"; import type { OriginatingChannelType, TemplateContext } from "../templating.js"; import { resolveResponseUsageMode, type VerboseLevel } from "../thinking.js"; import type { GetReplyOptions, ReplyPayload } from "../types.js"; @@ -41,6 +41,7 @@ import { createReplyToModeFilterForChannel, resolveReplyToMode } from "./reply-t import { incrementCompactionCount } from "./session-updates.js"; import type { TypingController } from "./typing.js"; import { createTypingSignaler } from "./typing-mode.js"; +import { emitDiagnosticEvent, isDiagnosticsEnabled } from "../../infra/diagnostic-events.js"; const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000; @@ -296,6 +297,7 @@ export async function runReplyAgent(params: { cleanupTranscripts: true, }); try { + const runStartedAt = Date.now(); const runOutcome = await runAgentTurnWithFallback({ commandBody, followupRun, @@ -403,6 +405,43 @@ export async function runReplyAgent(params: { activeSessionEntry?.contextTokens ?? DEFAULT_CONTEXT_TOKENS; + if (isDiagnosticsEnabled(cfg) && hasNonzeroUsage(usage)) { + const input = usage.input ?? 0; + const output = usage.output ?? 0; + const cacheRead = usage.cacheRead ?? 0; + const cacheWrite = usage.cacheWrite ?? 0; + const promptTokens = input + cacheRead + cacheWrite; + const totalTokens = usage.total ?? promptTokens + output; + const costConfig = resolveModelCostConfig({ + provider: providerUsed, + model: modelUsed, + config: cfg, + }); + const costUsd = estimateUsageCost({ usage, cost: costConfig }); + emitDiagnosticEvent({ + type: "model.usage", + sessionKey, + sessionId: followupRun.run.sessionId, + channel: replyToChannel, + provider: providerUsed, + model: modelUsed, + usage: { + input, + output, + cacheRead, + cacheWrite, + promptTokens, + total: totalTokens, + }, + context: { + limit: contextTokensUsed, + used: totalTokens, + }, + costUsd, + durationMs: Date.now() - runStartedAt, + }); + } + if (storePath && sessionKey) { if (hasNonzeroUsage(usage)) { try { diff --git a/src/config/schema.ts b/src/config/schema.ts index cafe28309..e905b7ead 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -47,6 +47,7 @@ export type ChannelUiMetadata = { const GROUP_LABELS: Record = { wizard: "Wizard", update: "Update", + diagnostics: "Diagnostics", logging: "Logging", gateway: "Gateway", agents: "Agents", @@ -73,6 +74,7 @@ const GROUP_LABELS: Record = { const GROUP_ORDER: Record = { wizard: 20, update: 25, + diagnostics: 27, gateway: 30, agents: 40, tools: 50, @@ -101,6 +103,17 @@ const FIELD_LABELS: Record = { "meta.lastTouchedAt": "Config Last Touched At", "update.channel": "Update Channel", "update.checkOnStart": "Update Check on Start", + "diagnostics.enabled": "Diagnostics Enabled", + "diagnostics.otel.enabled": "OpenTelemetry Enabled", + "diagnostics.otel.endpoint": "OpenTelemetry Endpoint", + "diagnostics.otel.protocol": "OpenTelemetry Protocol", + "diagnostics.otel.headers": "OpenTelemetry Headers", + "diagnostics.otel.serviceName": "OpenTelemetry Service Name", + "diagnostics.otel.traces": "OpenTelemetry Traces Enabled", + "diagnostics.otel.metrics": "OpenTelemetry Metrics Enabled", + "diagnostics.otel.logs": "OpenTelemetry Logs Enabled", + "diagnostics.otel.sampleRate": "OpenTelemetry Trace Sample Rate", + "diagnostics.otel.flushIntervalMs": "OpenTelemetry Flush Interval (ms)", "gateway.remote.url": "Remote Gateway URL", "gateway.remote.sshTarget": "Remote Gateway SSH Target", "gateway.remote.sshIdentity": "Remote Gateway SSH Identity", diff --git a/src/config/types.base.ts b/src/config/types.base.ts index 1f6c3ec6b..827ec5abb 100644 --- a/src/config/types.base.ts +++ b/src/config/types.base.ts @@ -102,6 +102,26 @@ export type LoggingConfig = { redactPatterns?: string[]; }; +export type DiagnosticsOtelConfig = { + enabled?: boolean; + endpoint?: string; + protocol?: "http/protobuf" | "grpc"; + headers?: Record; + serviceName?: string; + traces?: boolean; + metrics?: boolean; + logs?: boolean; + /** Trace sample rate (0.0 - 1.0). */ + sampleRate?: number; + /** Metric export interval (ms). */ + flushIntervalMs?: number; +}; + +export type DiagnosticsConfig = { + enabled?: boolean; + otel?: DiagnosticsOtelConfig; +}; + export type WebReconnectConfig = { initialMs?: number; maxMs?: number; diff --git a/src/config/types.clawdbot.ts b/src/config/types.clawdbot.ts index a3f6d816f..d80b74284 100644 --- a/src/config/types.clawdbot.ts +++ b/src/config/types.clawdbot.ts @@ -1,6 +1,6 @@ import type { AgentBinding, AgentsConfig } from "./types.agents.js"; import type { AuthConfig } from "./types.auth.js"; -import type { LoggingConfig, SessionConfig, WebConfig } from "./types.base.js"; +import type { DiagnosticsConfig, LoggingConfig, SessionConfig, WebConfig } from "./types.base.js"; import type { BrowserConfig } from "./types.browser.js"; import type { ChannelsConfig } from "./types.channels.js"; import type { CronConfig } from "./types.cron.js"; @@ -53,6 +53,7 @@ export type ClawdbotConfig = { lastRunCommand?: string; lastRunMode?: "local" | "remote"; }; + diagnostics?: DiagnosticsConfig; logging?: LoggingConfig; update?: { /** Update channel for git + npm installs ("stable", "beta", or "dev"). */ diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index dc2e7cf74..71b577599 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -38,6 +38,27 @@ export const ClawdbotSchema = z }) .strict() .optional(), + diagnostics: z + .object({ + enabled: z.boolean().optional(), + otel: z + .object({ + enabled: z.boolean().optional(), + endpoint: z.string().optional(), + protocol: z.union([z.literal("http/protobuf"), z.literal("grpc")]).optional(), + headers: z.record(z.string(), z.string()).optional(), + serviceName: z.string().optional(), + traces: z.boolean().optional(), + metrics: z.boolean().optional(), + logs: z.boolean().optional(), + sampleRate: z.number().min(0).max(1).optional(), + flushIntervalMs: z.number().int().nonnegative().optional(), + }) + .strict() + .optional(), + }) + .strict() + .optional(), logging: z .object({ level: z diff --git a/src/infra/diagnostic-events.test.ts b/src/infra/diagnostic-events.test.ts new file mode 100644 index 000000000..6d6806418 --- /dev/null +++ b/src/infra/diagnostic-events.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "vitest"; + +import { + emitDiagnosticEvent, + onDiagnosticEvent, + resetDiagnosticEventsForTest, +} from "./diagnostic-events.js"; + +describe("diagnostic-events", () => { + test("emits monotonic seq", async () => { + resetDiagnosticEventsForTest(); + const seqs: number[] = []; + const stop = onDiagnosticEvent((evt) => seqs.push(evt.seq)); + + emitDiagnosticEvent({ + type: "model.usage", + usage: { total: 1 }, + }); + emitDiagnosticEvent({ + type: "model.usage", + usage: { total: 2 }, + }); + + stop(); + + expect(seqs).toEqual([1, 2]); + }); +}); diff --git a/src/infra/diagnostic-events.ts b/src/infra/diagnostic-events.ts new file mode 100644 index 000000000..9e9b6e99c --- /dev/null +++ b/src/infra/diagnostic-events.ts @@ -0,0 +1,60 @@ +import type { ClawdbotConfig } from "../config/config.js"; + +export type DiagnosticUsageEvent = { + type: "model.usage"; + ts: number; + seq: number; + sessionKey?: string; + sessionId?: string; + channel?: string; + provider?: string; + model?: string; + usage: { + input?: number; + output?: number; + cacheRead?: number; + cacheWrite?: number; + promptTokens?: number; + total?: number; + }; + context?: { + limit?: number; + used?: number; + }; + costUsd?: number; + durationMs?: number; +}; + +export type DiagnosticEventPayload = DiagnosticUsageEvent; + +let seq = 0; +const listeners = new Set<(evt: DiagnosticEventPayload) => void>(); + +export function isDiagnosticsEnabled(config?: ClawdbotConfig): boolean { + return config?.diagnostics?.enabled === true; +} + +export function emitDiagnosticEvent(event: Omit) { + const enriched: DiagnosticEventPayload = { + ...event, + seq: (seq += 1), + ts: Date.now(), + }; + for (const listener of listeners) { + try { + listener(enriched); + } catch { + // Ignore listener failures. + } + } +} + +export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => void): () => void { + listeners.add(listener); + return () => listeners.delete(listener); +} + +export function resetDiagnosticEventsForTest(): void { + seq = 0; + listeners.clear(); +} diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 934d88c51..3814ae11c 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -58,7 +58,11 @@ export type { ChannelToolSend, } from "../channels/plugins/types.js"; export type { ChannelConfigSchema, ChannelPlugin } from "../channels/plugins/types.plugin.js"; -export type { ClawdbotPluginApi } from "../plugins/types.js"; +export type { + ClawdbotPluginApi, + ClawdbotPluginService, + ClawdbotPluginServiceContext, +} from "../plugins/types.js"; export type { PluginRuntime } from "../plugins/runtime/types.js"; export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; export type { ClawdbotConfig } from "../config/config.js"; @@ -178,6 +182,12 @@ export { formatDocsLink } from "../terminal/links.js"; export type { HookEntry } from "../hooks/types.js"; export { normalizeE164 } from "../utils.js"; export { missingTargetError } from "../infra/outbound/target-errors.js"; +export { + emitDiagnosticEvent, + isDiagnosticsEnabled, + onDiagnosticEvent, +} from "../infra/diagnostic-events.js"; +export type { DiagnosticEventPayload, DiagnosticUsageEvent } from "../infra/diagnostic-events.js"; // Channel: Discord export {