openclaw-plugin-yuanbao 2.15.0 → 2.15.2-beta.bd3f19cc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -349,6 +349,71 @@ Routing fields:
349
349
 
350
350
  ---
351
351
 
352
+ ## Observability (OpenTelemetry)
353
+
354
+ The plugin emits runtime metrics through the **OpenTelemetry API**. It does
355
+ **not** ship or configure an SDK/exporter itself — it reuses the global OTel
356
+ provider registered by OpenClaw's `diagnostics-otel` plugin. When that plugin
357
+ is not enabled, all telemetry calls are safe no-ops.
358
+
359
+ ### Emitted metrics (P0)
360
+
361
+ | Metric | Type | Attributes | Meaning |
362
+ | ----------------------------- | --------- | ----------------------- | ------------------------------------------ |
363
+ | `ws.connection.state` | Gauge | `account` | WS state: 0 disconnected … 4 connected |
364
+ | `ws.reconnect.count` | Counter | `account` | Reconnect attempts |
365
+ | `inbound.message.count` | Counter | `account`, `chat` | Inbound messages received |
366
+ | `pipeline.execution.duration` | Histogram | `chat` | End-to-end pipeline duration (ms) |
367
+ | `pipeline.error.count` | Counter | `chat`, `middleware`, `error` | Pipeline failures by middleware/error |
368
+ | `outbound.send.count` | Counter | `chat`, `result` | Outbound sends (`ok` / `fail` / `error`) |
369
+
370
+ > **Privacy red line:** metric attributes never carry message text, phone
371
+ > numbers, or PII. Only technical tags are emitted; `safeAttributes` drops any
372
+ > non-scalar value as a backstop.
373
+
374
+ ### Enable export to Tencent Cloud APM
375
+
376
+ Add the `diagnostics` + `plugins` blocks to `openclaw.json` (token is a
377
+ placeholder — replace once your APM application is provisioned):
378
+
379
+ ```json5
380
+ {
381
+ plugins: {
382
+ entries: {
383
+ "diagnostics-otel": { enabled: true },
384
+ },
385
+ },
386
+ diagnostics: {
387
+ enabled: true,
388
+ otel: {
389
+ enabled: true,
390
+ // Must include http:// prefix; see Tencent Cloud APM console
391
+ endpoint: "http://ap-guangzhou.apm.tencentcs.com:90/otlp",
392
+ protocol: "http/protobuf",
393
+ headers: { Authorization: "<PLACEHOLDER_TOKEN>" },
394
+ serviceName: "yuanbao-openclaw-plugin",
395
+ traces: true,
396
+ metrics: true,
397
+ logs: false,
398
+ // 10W-instance cost control: prioritize metrics, sample traces low
399
+ sampleRate: 0.1,
400
+ flushIntervalMs: 5000,
401
+ },
402
+ },
403
+ }
404
+ ```
405
+
406
+ Per-instance identity (to filter on the platform without logging into a single
407
+ machine) is injected via the standard env var before starting the gateway:
408
+
409
+ ```bash
410
+ export OTEL_RESOURCE_ATTRIBUTES="instance.id=<machine-id>"
411
+ ```
412
+
413
+ Reference: [Tencent Cloud · 接入 OpenClaw 应用](https://cloud.tencent.com/document/product/248/129686).
414
+
415
+ ---
416
+
352
417
  ## Configuration reference
353
418
 
354
419
  Full configuration: [Gateway configuration](/gateway/configuration)
@@ -3,6 +3,7 @@ import { buildSyncCommandsPayload } from "../../business/commands/command-sync/i
3
3
  import { handleInboundMessage } from "../../business/inbound/index.js";
4
4
  import { resolveTraceContext } from "../../business/trace/context.js";
5
5
  import { createLog } from "../../logger.js";
6
+ import { METRIC, WS_STATE_CODE, addCount, setGauge } from "../../infra/telemetry.js";
6
7
  import { getSignToken, forceRefreshSignToken } from "../api.js";
7
8
  import { decodeInboundMessage } from "./biz-codec.js";
8
9
  import { YuanbaoWsClient } from "./client.js";
@@ -57,6 +58,12 @@ export async function startYuanbaoWsGateway(params) {
57
58
  },
58
59
  onStateChange: (state) => {
59
60
  gwlog.info(`[${account.accountId}] WS state: ${state}`);
61
+ setGauge(METRIC.wsConnectionState, WS_STATE_CODE[state] ?? -1, {
62
+ account: account.accountId,
63
+ });
64
+ if (state === "reconnecting") {
65
+ addCount(METRIC.wsReconnectCount, 1, { account: account.accountId });
66
+ }
60
67
  statusSink?.({
61
68
  wsState: state,
62
69
  connected: state === "connected",
@@ -288,6 +295,7 @@ function handleWsDispatchEvent(params) {
288
295
  return;
289
296
  }
290
297
  const { msg, chatType } = converted;
298
+ addCount(METRIC.inboundMessageCount, 1, { account: account.accountId, chat: chatType });
291
299
  // Resolve / generate trace context
292
300
  const traceContext = resolveTraceContext({
293
301
  traceId: msg.trace_id,
@@ -16,4 +16,5 @@ export declare class MessagePipeline {
16
16
  remove(name: string): this;
17
17
  /** Execute the pipeline */
18
18
  execute(ctx: PipelineContext): Promise<void>;
19
+ private runMiddlewareChain;
19
20
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Onion-model middleware engine with conditional guards (when) and named insert/remove support.
5
5
  */
6
+ import { METRIC, SPAN, addCount, recordDuration, withActiveSpan } from "../../infra/telemetry.js";
6
7
  export class MessagePipeline {
7
8
  middlewares = [];
8
9
  /** Register middleware at the end of the pipeline */
@@ -42,8 +43,17 @@ export class MessagePipeline {
42
43
  }
43
44
  /** Execute the pipeline */
44
45
  async execute(ctx) {
46
+ const chat = ctx.isGroup ? "group" : "c2c";
47
+ const incomingTraceId = ctx.raw.trace_id?.trim();
48
+ await withActiveSpan(SPAN.pipelineExecute, {
49
+ traceId: incomingTraceId,
50
+ attributes: { chat, account: ctx.account.accountId },
51
+ }, async () => this.runMiddlewareChain(ctx, chat));
52
+ }
53
+ async runMiddlewareChain(ctx, chat) {
45
54
  const chain = this.middlewares;
46
55
  let index = 0;
56
+ const startedAt = Date.now();
47
57
  const next = async () => {
48
58
  while (index < chain.length) {
49
59
  const mw = chain[index++];
@@ -56,11 +66,21 @@ export class MessagePipeline {
56
66
  }
57
67
  catch (err) {
58
68
  ctx.log.error(`middleware [${mw.name}] execution error`, { error: String(err) });
69
+ addCount(METRIC.pipelineErrorCount, 1, {
70
+ chat,
71
+ middleware: mw.name,
72
+ error: err instanceof Error ? err.name : "unknown",
73
+ });
59
74
  throw err;
60
75
  }
61
76
  return;
62
77
  }
63
78
  };
64
- await next();
79
+ try {
80
+ await next();
81
+ }
82
+ finally {
83
+ recordDuration(METRIC.pipelineExecutionDuration, Date.now() - startedAt, { chat });
84
+ }
65
85
  }
66
86
  }
@@ -2,7 +2,7 @@
2
2
  * Middleware: parse or generate trace context from inbound message trace_id / seq_id.
3
3
  * Injected into ctx.traceContext for downstream middlewares and transport layer.
4
4
  */
5
- import { resolveTraceContext } from "../../trace/context.js";
5
+ import { getActiveTraceparent, resolveTraceContext } from "../../trace/context.js";
6
6
  export const resolveTrace = {
7
7
  name: "resolve-trace",
8
8
  handler: async (ctx, next) => {
@@ -11,9 +11,14 @@ export const resolveTrace = {
11
11
  traceId: ctx.raw.trace_id,
12
12
  seqId: ctx.raw.seq_id ?? ctx.raw.msg_seq,
13
13
  });
14
+ const activeTraceparent = getActiveTraceparent();
15
+ if (activeTraceparent) {
16
+ ctx.traceContext = { ...ctx.traceContext, traceparent: activeTraceparent };
17
+ }
14
18
  ctx.log.debug("[resolve-trace] trace context resolved", {
15
19
  traceId: ctx.traceContext.traceId,
16
20
  seqId: ctx.traceContext.seqId ?? "(none)",
21
+ traceparent: ctx.traceContext.traceparent,
17
22
  });
18
23
  await next();
19
24
  },
@@ -1,3 +1,4 @@
1
+ import type { Context } from "@opentelemetry/api";
1
2
  export type YuanbaoTraceContext = {
2
3
  traceId: string;
3
4
  traceparent: string;
@@ -9,6 +10,13 @@ export type YuanbaoTraceContext = {
9
10
  * Generate a random trace ID (32-char hex string).
10
11
  * Used as fallback when inbound message has no trace_id.
11
12
  */ export declare function generateTraceId(): string;
13
+ /**
14
+ * Build an OTel parent context that links a new span to an inbound trace id.
15
+ * Used when the upstream only provides trace_id (no parent span id).
16
+ */
17
+ export declare function buildRemoteParentOtelContext(traceId: string): Context;
18
+ /** Read the W3C traceparent for the currently active OTel span, if any. */
19
+ export declare function getActiveTraceparent(): string | undefined;
12
20
  /**
13
21
  * Resolve or generate a complete trace context.
14
22
  * Prefers inbound traceId; generates one if missing.
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
+ import { context, ROOT_CONTEXT, trace, TraceFlags, } from "@opentelemetry/api";
3
4
  import { createLog } from "../../logger.js";
4
5
  const traceStorage = new AsyncLocalStorage();
5
6
  const EMPTY_TRACE_ID = "0".repeat(32);
@@ -34,6 +35,28 @@ function normalizeTraceIdForTraceparent(traceId) {
34
35
  function buildTraceparent(traceId) {
35
36
  return `00-${normalizeTraceIdForTraceparent(traceId)}-${generateHex(8)}-01`;
36
37
  }
38
+ /**
39
+ * Build an OTel parent context that links a new span to an inbound trace id.
40
+ * Used when the upstream only provides trace_id (no parent span id).
41
+ */
42
+ export function buildRemoteParentOtelContext(traceId) {
43
+ const normalizedTraceId = normalizeTraceIdForTraceparent(traceId);
44
+ return trace.setSpanContext(ROOT_CONTEXT, {
45
+ traceId: normalizedTraceId,
46
+ spanId: generateHex(8),
47
+ traceFlags: TraceFlags.SAMPLED,
48
+ isRemote: true,
49
+ });
50
+ }
51
+ /** Read the W3C traceparent for the currently active OTel span, if any. */
52
+ export function getActiveTraceparent() {
53
+ const spanContext = trace.getSpan(context.active())?.spanContext();
54
+ if (!spanContext || !trace.isSpanContextValid(spanContext)) {
55
+ return undefined;
56
+ }
57
+ const flags = spanContext.traceFlags.toString(16).padStart(2, "0");
58
+ return `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`;
59
+ }
37
60
  function normalizeSeqId(seqId) {
38
61
  if (seqId === undefined || seqId === null) {
39
62
  return undefined;
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "module";
2
2
  import os from "os";
3
3
  import semver from "semver";
4
+ import { setTelemetryVersion } from "./telemetry.js";
4
5
  /**
5
6
  * Plugin version number.
6
7
  */
@@ -55,6 +56,7 @@ export const initEnv = (api) => {
55
56
  if (!_pluginVersion || !_openclawVersion) {
56
57
  legacyInitEnv();
57
58
  }
59
+ setTelemetryVersion(_pluginVersion);
58
60
  // Runtime compatibility guard (Layer 4 defense)
59
61
  if (_openclawVersion) {
60
62
  assertHostVersionCompatible(_openclawVersion);
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Telemetry helpers — thin wrapper over `@opentelemetry/api`.
3
+ *
4
+ * The plugin only uses the OTel **API** package; the global SDK / exporter is
5
+ * registered by OpenClaw's `diagnostics-otel` plugin. When no global provider
6
+ * is present (local dev, tests, diagnostics disabled) every call degrades to a
7
+ * no-op automatically — the API ships no-op meters by default.
8
+ *
9
+ * Compliance red line: metric attributes must NEVER carry message text, phone
10
+ * numbers, or any PII. `safeAttributes` strips non-scalar values and truncates
11
+ * strings as a backstop, but callers are still responsible for only passing
12
+ * technical tags (state, result, chat type, middleware name, error class, …).
13
+ */
14
+ import type { Attributes } from "@opentelemetry/api";
15
+ /** Stable metric instrument names (dotted, OTel-style). */
16
+ export declare const METRIC: {
17
+ readonly wsConnectionState: "ws.connection.state";
18
+ readonly wsReconnectCount: "ws.reconnect.count";
19
+ readonly inboundMessageCount: "inbound.message.count";
20
+ readonly pipelineExecutionDuration: "pipeline.execution.duration";
21
+ readonly pipelineErrorCount: "pipeline.error.count";
22
+ readonly outboundSendCount: "outbound.send.count";
23
+ };
24
+ /** Stable span names (dotted, OTel-style). */
25
+ export declare const SPAN: {
26
+ readonly pipelineExecute: "pipeline.execute";
27
+ };
28
+ /** Numeric encoding for the WS connection-state gauge. */
29
+ export declare const WS_STATE_CODE: Record<string, number>;
30
+ /** Set the meter version (plugin version); optional, called once at init. */
31
+ export declare function setTelemetryVersion(version: string): void;
32
+ /**
33
+ * Drop anything that isn't a scalar (objects/arrays could smuggle PII) and
34
+ * truncate long strings. Returns undefined when there is nothing to keep.
35
+ */
36
+ export declare function safeAttributes(attrs?: Attributes): Attributes | undefined;
37
+ /** Increment a counter (default +1). */
38
+ export declare function addCount(name: string, value?: number, attrs?: Attributes): void;
39
+ /** Record a histogram value (e.g. a duration in ms). */
40
+ export declare function recordDuration(name: string, value: number, attrs?: Attributes): void;
41
+ /** Set the current value of a gauge. */
42
+ export declare function setGauge(name: string, value: number, attrs?: Attributes): void;
43
+ /**
44
+ * Run `fn` inside an active OTel span. Prefers inbound `traceId` (server logExt)
45
+ * over W3C `traceparent`: locally generated traceparent uses a random span id
46
+ * that was never exported, so linking via trace_id keeps APM correlation aligned
47
+ * with the backend. Falls back to traceparent when traceId is absent.
48
+ */
49
+ export declare function withActiveSpan<T>(name: string, params: {
50
+ attributes?: Attributes;
51
+ traceId?: string;
52
+ traceparent?: string;
53
+ }, fn: () => Promise<T>): Promise<T>;
54
+ /** Reset cached instruments — test-only helper. */
55
+ export declare function __resetTelemetryForTest(): void;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Telemetry helpers — thin wrapper over `@opentelemetry/api`.
3
+ *
4
+ * The plugin only uses the OTel **API** package; the global SDK / exporter is
5
+ * registered by OpenClaw's `diagnostics-otel` plugin. When no global provider
6
+ * is present (local dev, tests, diagnostics disabled) every call degrades to a
7
+ * no-op automatically — the API ships no-op meters by default.
8
+ *
9
+ * Compliance red line: metric attributes must NEVER carry message text, phone
10
+ * numbers, or any PII. `safeAttributes` strips non-scalar values and truncates
11
+ * strings as a backstop, but callers are still responsible for only passing
12
+ * technical tags (state, result, chat type, middleware name, error class, …).
13
+ */
14
+ import { context, metrics, propagation, ROOT_CONTEXT, SpanKind, SpanStatusCode, trace, TraceFlags, } from "@opentelemetry/api";
15
+ import { buildRemoteParentOtelContext } from "../business/trace/context.js";
16
+ const METER_NAME = "openclaw-plugin-yuanbao";
17
+ /** Stable metric instrument names (dotted, OTel-style). */
18
+ export const METRIC = {
19
+ wsConnectionState: "ws.connection.state",
20
+ wsReconnectCount: "ws.reconnect.count",
21
+ inboundMessageCount: "inbound.message.count",
22
+ pipelineExecutionDuration: "pipeline.execution.duration",
23
+ pipelineErrorCount: "pipeline.error.count",
24
+ outboundSendCount: "outbound.send.count",
25
+ };
26
+ /** Stable span names (dotted, OTel-style). */
27
+ export const SPAN = {
28
+ pipelineExecute: "pipeline.execute",
29
+ };
30
+ /** Numeric encoding for the WS connection-state gauge. */
31
+ export const WS_STATE_CODE = {
32
+ disconnected: 0,
33
+ connecting: 1,
34
+ authenticating: 2,
35
+ reconnecting: 3,
36
+ connected: 4,
37
+ };
38
+ let meterVersion = "0.0.0";
39
+ /** Set the meter version (plugin version); optional, called once at init. */
40
+ export function setTelemetryVersion(version) {
41
+ if (version) {
42
+ meterVersion = version;
43
+ }
44
+ }
45
+ function getMeter() {
46
+ return metrics.getMeter(METER_NAME, meterVersion);
47
+ }
48
+ function getTracer() {
49
+ return trace.getTracer(METER_NAME, meterVersion);
50
+ }
51
+ const counters = new Map();
52
+ const histograms = new Map();
53
+ const gauges = new Map();
54
+ function getCounter(name) {
55
+ let c = counters.get(name);
56
+ if (!c) {
57
+ c = getMeter().createCounter(name);
58
+ counters.set(name, c);
59
+ }
60
+ return c;
61
+ }
62
+ function getHistogram(name) {
63
+ let h = histograms.get(name);
64
+ if (!h) {
65
+ h = getMeter().createHistogram(name);
66
+ histograms.set(name, h);
67
+ }
68
+ return h;
69
+ }
70
+ function getGauge(name) {
71
+ let g = gauges.get(name);
72
+ if (!g) {
73
+ g = getMeter().createGauge(name);
74
+ gauges.set(name, g);
75
+ }
76
+ return g;
77
+ }
78
+ const MAX_ATTR_STR_LEN = 128;
79
+ /**
80
+ * Drop anything that isn't a scalar (objects/arrays could smuggle PII) and
81
+ * truncate long strings. Returns undefined when there is nothing to keep.
82
+ */
83
+ export function safeAttributes(attrs) {
84
+ if (!attrs) {
85
+ return undefined;
86
+ }
87
+ const out = {};
88
+ let kept = false;
89
+ for (const [key, value] of Object.entries(attrs)) {
90
+ if (value == null) {
91
+ continue;
92
+ }
93
+ if (typeof value === "string") {
94
+ out[key] = value.length > MAX_ATTR_STR_LEN ? value.slice(0, MAX_ATTR_STR_LEN) : value;
95
+ kept = true;
96
+ }
97
+ else if (typeof value === "number" || typeof value === "boolean") {
98
+ out[key] = value;
99
+ kept = true;
100
+ }
101
+ // objects / arrays are intentionally dropped to avoid leaking content
102
+ }
103
+ return kept ? out : undefined;
104
+ }
105
+ /** Increment a counter (default +1). */
106
+ export function addCount(name, value = 1, attrs) {
107
+ getCounter(name).add(value, safeAttributes(attrs));
108
+ }
109
+ /** Record a histogram value (e.g. a duration in ms). */
110
+ export function recordDuration(name, value, attrs) {
111
+ getHistogram(name).record(value, safeAttributes(attrs));
112
+ }
113
+ /** Set the current value of a gauge. */
114
+ export function setGauge(name, value, attrs) {
115
+ getGauge(name).record(value, safeAttributes(attrs));
116
+ }
117
+ /**
118
+ * Run `fn` inside an active OTel span. Prefers inbound `traceId` (server logExt)
119
+ * over W3C `traceparent`: locally generated traceparent uses a random span id
120
+ * that was never exported, so linking via trace_id keeps APM correlation aligned
121
+ * with the backend. Falls back to traceparent when traceId is absent.
122
+ */
123
+ export async function withActiveSpan(name, params, fn) {
124
+ let parentCtx = ROOT_CONTEXT;
125
+ const traceparent = params.traceparent?.trim();
126
+ const traceId = params.traceId?.trim();
127
+ if (traceId) {
128
+ parentCtx = buildRemoteParentOtelContext(traceId);
129
+ }
130
+ else if (traceparent) {
131
+ parentCtx = propagation.extract(ROOT_CONTEXT, { traceparent });
132
+ }
133
+ const span = getTracer().startSpan(name, {
134
+ kind: SpanKind.SERVER,
135
+ attributes: safeAttributes(params.attributes),
136
+ }, parentCtx);
137
+ return context.with(trace.setSpan(parentCtx, span), async () => {
138
+ try {
139
+ const result = await fn();
140
+ span.setStatus({ code: SpanStatusCode.OK });
141
+ return result;
142
+ }
143
+ catch (err) {
144
+ const message = err instanceof Error ? err.message : String(err);
145
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
146
+ if (err instanceof Error) {
147
+ span.recordException(err);
148
+ }
149
+ throw err;
150
+ }
151
+ finally {
152
+ span.end();
153
+ }
154
+ });
155
+ }
156
+ /** Reset cached instruments — test-only helper. */
157
+ export function __resetTelemetryForTest() {
158
+ counters.clear();
159
+ histograms.clear();
160
+ gauges.clear();
161
+ meterVersion = "0.0.0";
162
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createLog } from "../logger.js";
7
7
  import { getMember } from "./cache/member.js";
8
+ import { METRIC, addCount } from "./telemetry.js";
8
9
  import { InMemoryTtlDb } from "./cache/ttl-db.js";
9
10
  import { classifyReplyMode } from "./reply-classify.js";
10
11
  const firstReplyRefDb = new InMemoryTtlDb({
@@ -59,6 +60,7 @@ export async function sendC2CMsgBody(params) {
59
60
  messageId: result.msgId,
60
61
  error: result.code !== 0 ? result.message || `code: ${result.code}` : undefined,
61
62
  };
63
+ addCount(METRIC.outboundSendCount, 1, { chat: "c2c", result: sendResult.ok ? "ok" : "fail" });
62
64
  if (!sendResult.ok) {
63
65
  log.error("[C2C] send failed", { error: sendResult.error });
64
66
  }
@@ -69,6 +71,7 @@ export async function sendC2CMsgBody(params) {
69
71
  }
70
72
  catch (err) {
71
73
  const error = err instanceof Error ? err.message : String(err);
74
+ addCount(METRIC.outboundSendCount, 1, { chat: "c2c", result: "error" });
72
75
  log.error("[C2C] send error", { error });
73
76
  return { ok: false, error };
74
77
  }
@@ -95,6 +98,7 @@ export async function sendGroupMsgBody(params) {
95
98
  messageId: result.msgId,
96
99
  error: result.code !== 0 ? result.message || `code: ${result.code}` : undefined,
97
100
  };
101
+ addCount(METRIC.outboundSendCount, 1, { chat: "group", result: sendResult.ok ? "ok" : "fail" });
98
102
  if (!sendResult.ok) {
99
103
  log.error("[group] send failed", { error: sendResult.error });
100
104
  }
@@ -105,6 +109,7 @@ export async function sendGroupMsgBody(params) {
105
109
  }
106
110
  catch (err) {
107
111
  const error = err instanceof Error ? err.message : String(err);
112
+ addCount(METRIC.outboundSendCount, 1, { chat: "group", result: "error" });
108
113
  log.error("[group] send error", { error });
109
114
  return { ok: false, error };
110
115
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "openclaw-plugin-yuanbao",
3
- "version": "2.15.0",
3
+ "version": "2.15.2-beta.bd3f19cc",
4
4
  "name": "YuanBao",
5
5
  "description": "YuanBao channel plugin",
6
6
  "channels": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-yuanbao",
3
- "version": "2.15.0",
3
+ "version": "2.15.2-beta.bd3f19cc",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Tencent YuanBao intelligent bot channel plugin",
@@ -20,6 +20,7 @@
20
20
  ],
21
21
  "author": "Tencent YuanBao Team",
22
22
  "dependencies": {
23
+ "@opentelemetry/api": "^1.9.1",
23
24
  "protobufjs": "^7.5.4",
24
25
  "semver": "^7.7.4",
25
26
  "ws": "^8.20.0"