@openclaw/diagnostics-otel 2026.5.24-beta.2 → 2026.5.26-beta.1

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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isValidDiagnosticSpanId, isValidDiagnosticTraceFlags, isValidDiagnosticTraceId, redactSensitiveText } from "./api.js";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
- import { SpanStatusCode, TraceFlags, context, metrics, trace } from "@opentelemetry/api";
3
+ import { SpanKind, SpanStatusCode, TraceFlags, context, metrics, trace } from "@opentelemetry/api";
4
4
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
5
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
6
6
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
@@ -10,6 +10,7 @@ import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
10
10
  import { NodeSDK } from "@opentelemetry/sdk-node";
11
11
  import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
12
12
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
13
+ import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, ATTR_GEN_AI_TOOL_DEFINITIONS } from "@opentelemetry/semantic-conventions/incubating";
13
14
  import { registerUnhandledRejectionHandler } from "openclaw/plugin-sdk/runtime-env";
14
15
  //#region extensions/diagnostics-otel/src/service.ts
15
16
  const DEFAULT_SERVICE_NAME = "openclaw";
@@ -36,8 +37,8 @@ const DROPPED_OTEL_ATTRIBUTE_KEYS = new Set([
36
37
  "openclaw.trace_id"
37
38
  ]);
38
39
  const LOW_CARDINALITY_VALUE_RE = /^[A-Za-z0-9_.:-]{1,120}$/u;
39
- const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS = 4 * 1024;
40
- const MAX_OTEL_CONTENT_ARRAY_ITEMS = 16;
40
+ const MAX_OTEL_CONTENT_ATTRIBUTE_CHARS = 128 * 1024;
41
+ const MAX_OTEL_CONTENT_ARRAY_ITEMS = 200;
41
42
  const MAX_OTEL_LOG_BODY_CHARS = 4 * 1024;
42
43
  const MAX_OTEL_LOG_ATTRIBUTE_COUNT = 64;
43
44
  const MAX_OTEL_LOG_ATTRIBUTE_VALUE_CHARS = 4 * 1024;
@@ -94,6 +95,7 @@ const NO_CONTENT_CAPTURE = {
94
95
  toolInputs: false,
95
96
  toolOutputs: false,
96
97
  systemPrompt: false,
98
+ toolDefinitions: false,
97
99
  logBodies: false
98
100
  };
99
101
  function normalizeEndpoint(endpoint) {
@@ -104,6 +106,11 @@ function resolveOtelUrl(endpoint, path) {
104
106
  if (!endpoint) return;
105
107
  const endpointWithoutQueryOrFragment = endpoint.split(/[?#]/, 1)[0] ?? endpoint;
106
108
  if (/\/v1\/(?:traces|metrics|logs)$/i.test(endpointWithoutQueryOrFragment)) return endpoint;
109
+ if (/[?#]/u.test(endpoint)) try {
110
+ const url = new URL(endpoint);
111
+ url.pathname = `${url.pathname.replace(/\/+$/u, "")}/${path}`;
112
+ return url.toString();
113
+ } catch {}
107
114
  return `${endpoint}/${path}`;
108
115
  }
109
116
  function resolveSignalOtelUrl(params) {
@@ -231,6 +238,13 @@ function assignGenAiSpanIdentityAttrs(attrs, input) {
231
238
  function assignGenAiModelCallAttrs(attrs, evt) {
232
239
  assignGenAiSpanIdentityAttrs(attrs, evt);
233
240
  }
241
+ function modelCallSpanName(evt) {
242
+ if (!emitLatestGenAiSemconv()) return "openclaw.model.call";
243
+ return `${genAiOperationName(evt.api)} ${lowCardinalityAttr(evt.model)}`;
244
+ }
245
+ function modelCallSpanKind() {
246
+ return emitLatestGenAiSemconv() ? SpanKind.CLIENT : void 0;
247
+ }
234
248
  function addUpstreamRequestIdSpanEvent(span, upstreamRequestIdHash) {
235
249
  if (!upstreamRequestIdHash) return;
236
250
  const boundedHash = lowCardinalityAttr(upstreamRequestIdHash);
@@ -250,6 +264,7 @@ function resolveContentCapturePolicy(value) {
250
264
  toolInputs: true,
251
265
  toolOutputs: true,
252
266
  systemPrompt: false,
267
+ toolDefinitions: true,
253
268
  logBodies: true
254
269
  };
255
270
  if (!value || typeof value !== "object" || Array.isArray(value)) return NO_CONTENT_CAPTURE;
@@ -261,6 +276,7 @@ function resolveContentCapturePolicy(value) {
261
276
  toolInputs: config.toolInputs === true,
262
277
  toolOutputs: config.toolOutputs === true,
263
278
  systemPrompt: config.systemPrompt === true,
279
+ toolDefinitions: config.toolDefinitions === true,
264
280
  logBodies: false
265
281
  };
266
282
  }
@@ -274,15 +290,276 @@ function normalizeOtelContentValue(value) {
274
290
  for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) if (typeof item === "string") items.push(item);
275
291
  if (items.length > 0) return normalizeOtelLogString(items.join("\n"), MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
276
292
  }
293
+ const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
294
+ if (json) return json;
295
+ }
296
+ const TRUNCATED_JSON_TEXT_SUFFIX = "...(truncated)";
297
+ const JSON_TRUNCATION_STRING_BUDGETS = [
298
+ 8192,
299
+ 4096,
300
+ 2048,
301
+ 1024,
302
+ 512,
303
+ 256,
304
+ 128,
305
+ 64,
306
+ 32
307
+ ];
308
+ const JSON_TRUNCATION_ARRAY_ITEM_BUDGETS = [
309
+ MAX_OTEL_CONTENT_ARRAY_ITEMS,
310
+ 100,
311
+ 50,
312
+ 25,
313
+ 10,
314
+ 5,
315
+ 1
316
+ ];
317
+ const JSON_TRUNCATION_MAX_OBJECT_FIELDS = 64;
318
+ const JSON_TRUNCATION_MAX_DEPTH = 8;
319
+ function safeJsonString(value, maxChars) {
320
+ if (value === void 0 || typeof value === "function" || typeof value === "symbol") return;
321
+ const exact = stringifyJsonForOtelAttribute(value);
322
+ if (exact && exact.length <= maxChars) return exact;
323
+ for (const maxArrayItems of JSON_TRUNCATION_ARRAY_ITEM_BUDGETS) for (const maxStringChars of JSON_TRUNCATION_STRING_BUDGETS) {
324
+ const json = stringifyJsonForOtelAttribute(truncateJsonValueForOtelAttribute(value, {
325
+ maxArrayItems,
326
+ maxDepth: JSON_TRUNCATION_MAX_DEPTH,
327
+ maxObjectFields: JSON_TRUNCATION_MAX_OBJECT_FIELDS,
328
+ maxStringChars,
329
+ seen: /* @__PURE__ */ new WeakSet()
330
+ }));
331
+ if (json && json.length <= maxChars) return json;
332
+ }
333
+ const summary = stringifyJsonForOtelAttribute({
334
+ truncated: true,
335
+ reason: exact ? "max_attribute_size" : "unserializable_value",
336
+ type: describeJsonValue(value)
337
+ });
338
+ return summary && summary.length <= maxChars ? summary : void 0;
339
+ }
340
+ function stringifyJsonForOtelAttribute(value) {
341
+ try {
342
+ const json = JSON.stringify(value);
343
+ if (!json) return;
344
+ return redactSensitiveText(json);
345
+ } catch {
346
+ return;
347
+ }
348
+ }
349
+ function truncateJsonValueForOtelAttribute(value, options) {
350
+ if (typeof value === "string") return truncateJsonTextForOtelAttribute(value, options.maxStringChars);
351
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
352
+ if (typeof value === "bigint") return truncateJsonTextForOtelAttribute(String(value), options.maxStringChars);
353
+ if (value === void 0 || typeof value === "function" || typeof value === "symbol") return;
354
+ if (options.maxDepth <= 0) return {
355
+ truncated: true,
356
+ reason: "max_depth"
357
+ };
358
+ if (Array.isArray(value)) return truncateJsonArrayForOtelAttribute(value, options);
359
+ if (typeof value === "object") return truncateJsonObjectForOtelAttribute(value, options);
360
+ }
361
+ function truncateJsonArrayForOtelAttribute(value, options) {
362
+ if (options.seen.has(value)) return [{
363
+ truncated: true,
364
+ reason: "circular_reference"
365
+ }];
366
+ options.seen.add(value);
367
+ const nextOptions = {
368
+ ...options,
369
+ maxDepth: options.maxDepth - 1
370
+ };
371
+ const items = value.slice(0, options.maxArrayItems).map((item) => truncateJsonValueForOtelAttribute(item, nextOptions));
372
+ if (value.length > items.length) items.push({
373
+ truncated: true,
374
+ omittedItems: value.length - items.length
375
+ });
376
+ options.seen.delete(value);
377
+ return items;
378
+ }
379
+ function truncateJsonObjectForOtelAttribute(value, options) {
380
+ if (options.seen.has(value)) return {
381
+ truncated: true,
382
+ reason: "circular_reference"
383
+ };
384
+ options.seen.add(value);
385
+ const nextOptions = {
386
+ ...options,
387
+ maxDepth: options.maxDepth - 1
388
+ };
389
+ const result = {};
390
+ const entries = Object.entries(value).filter(([, field]) => field !== void 0 && typeof field !== "function" && typeof field !== "symbol");
391
+ for (const [key, field] of entries.slice(0, options.maxObjectFields)) result[key] = truncateJsonValueForOtelAttribute(field, nextOptions);
392
+ if (entries.length > options.maxObjectFields) {
393
+ result.truncated = true;
394
+ result.omittedFields = entries.length - options.maxObjectFields;
395
+ }
396
+ options.seen.delete(value);
397
+ return result;
398
+ }
399
+ function truncateJsonTextForOtelAttribute(value, maxChars) {
400
+ const redacted = redactSensitiveText(value);
401
+ if (redacted.length <= maxChars) return redacted;
402
+ const suffixBudget = Math.min(14, maxChars);
403
+ const prefixBudget = Math.max(0, maxChars - suffixBudget);
404
+ return `${redacted.slice(0, prefixBudget)}${TRUNCATED_JSON_TEXT_SUFFIX.slice(14 - suffixBudget)}`;
405
+ }
406
+ function describeJsonValue(value) {
407
+ if (Array.isArray(value)) return "array";
408
+ if (value === null) return "null";
409
+ return typeof value;
410
+ }
411
+ function isRecord(value) {
412
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
413
+ }
414
+ function textPart(content) {
415
+ return {
416
+ type: "text",
417
+ content
418
+ };
419
+ }
420
+ function toolCallResponsePart(part) {
421
+ return {
422
+ type: "tool_call_response",
423
+ ...typeof part.id === "string" ? { id: part.id } : {},
424
+ result: part.result ?? part.response ?? part.content ?? part.details ?? ""
425
+ };
426
+ }
427
+ function contentParts(value) {
428
+ if (typeof value === "string") return value.length > 0 ? [textPart(value)] : [];
429
+ if (!Array.isArray(value)) {
430
+ if (value === void 0 || value === null) return [];
431
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return [textPart(String(value))];
432
+ const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
433
+ return json ? [textPart(json)] : [];
434
+ }
435
+ const parts = [];
436
+ for (const part of value) {
437
+ if (typeof part === "string") {
438
+ if (part.length > 0) parts.push(textPart(part));
439
+ continue;
440
+ }
441
+ if (!isRecord(part)) continue;
442
+ if (part.type === "text" && typeof part.text === "string") parts.push(textPart(part.text));
443
+ else if (part.type === "text" && typeof part.content === "string") parts.push(textPart(part.content));
444
+ else if (part.type === "thinking" && typeof part.thinking === "string") parts.push({
445
+ type: "reasoning",
446
+ content: part.thinking
447
+ });
448
+ else if (part.type === "toolCall" && typeof part.name === "string") parts.push({
449
+ type: "tool_call",
450
+ name: part.name,
451
+ ...typeof part.id === "string" ? { id: part.id } : {},
452
+ ...part.arguments !== void 0 ? { arguments: part.arguments } : {}
453
+ });
454
+ else if (part.type === "tool_call" && typeof part.name === "string") parts.push({
455
+ type: "tool_call",
456
+ name: part.name,
457
+ ...typeof part.id === "string" ? { id: part.id } : {},
458
+ ...part.arguments !== void 0 ? { arguments: part.arguments } : {}
459
+ });
460
+ else if (part.type === "tool_call_response") parts.push(toolCallResponsePart(part));
461
+ else if (part.type === "image") {
462
+ const data = typeof part.data === "string" ? part.data : void 0;
463
+ parts.push({
464
+ type: "blob",
465
+ modality: "image",
466
+ ...typeof part.mimeType === "string" ? { mime_type: part.mimeType } : {},
467
+ ...typeof part.mime_type === "string" ? { mime_type: part.mime_type } : {},
468
+ ...data ? { content: data } : {}
469
+ });
470
+ }
471
+ }
472
+ return parts;
473
+ }
474
+ function normalizeGenAiMessage(value, fallbackRole = "user") {
475
+ if (typeof value === "string") return {
476
+ role: fallbackRole,
477
+ parts: [textPart(value)]
478
+ };
479
+ if (!isRecord(value)) return;
480
+ const rawRole = typeof value.role === "string" ? value.role : fallbackRole;
481
+ const role = rawRole === "toolResult" ? "tool" : rawRole;
482
+ let parts;
483
+ if (role === "tool") {
484
+ const explicitParts = contentParts(value.parts);
485
+ parts = explicitParts.length > 0 ? explicitParts : [toolCallResponsePart({
486
+ id: value.toolCallId,
487
+ result: value.content ?? value.details ?? ""
488
+ })];
489
+ } else parts = contentParts(value.parts ?? value.content);
490
+ if (parts.length === 0) return;
491
+ return {
492
+ role,
493
+ parts,
494
+ ...typeof value.name === "string" ? { name: value.name } : {},
495
+ ...typeof value.finish_reason === "string" ? { finish_reason: value.finish_reason } : {},
496
+ ...typeof value.stopReason === "string" ? { finish_reason: value.stopReason } : {}
497
+ };
498
+ }
499
+ function normalizeGenAiMessages(value, fallbackRole) {
500
+ const source = Array.isArray(value) ? value : value === void 0 ? [] : [value];
501
+ const messages = [];
502
+ for (const item of source.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) {
503
+ const message = normalizeGenAiMessage(item, fallbackRole);
504
+ if (message) messages.push(message);
505
+ }
506
+ return messages;
507
+ }
508
+ function normalizeGenAiToolDefinition(value) {
509
+ if (!isRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
510
+ return {
511
+ type: typeof value.type === "string" ? value.type : "function",
512
+ name: value.name,
513
+ ...typeof value.description === "string" ? { description: value.description } : {},
514
+ ...value.parameters !== void 0 ? { parameters: value.parameters } : {}
515
+ };
516
+ }
517
+ function normalizeGenAiToolDefinitions(value) {
518
+ if (!Array.isArray(value)) return [];
519
+ const definitions = [];
520
+ for (const item of value.slice(0, MAX_OTEL_CONTENT_ARRAY_ITEMS)) {
521
+ const definition = normalizeGenAiToolDefinition(item);
522
+ if (definition) definitions.push(definition);
523
+ }
524
+ return definitions;
525
+ }
526
+ function assignJsonAttribute(attributes, key, value) {
527
+ const json = safeJsonString(value, MAX_OTEL_CONTENT_ATTRIBUTE_CHARS);
528
+ if (json) attributes[key] = json;
529
+ }
530
+ function assignGenAiModelContentAttributes(attributes, content, policy) {
531
+ if (policy.systemPrompt && typeof content?.systemPrompt === "string") assignJsonAttribute(attributes, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, [textPart(content.systemPrompt)]);
532
+ if (policy.inputMessages) {
533
+ const inputMessages = normalizeGenAiMessages(content?.inputMessages, "user");
534
+ if (inputMessages.length > 0) {
535
+ assignJsonAttribute(attributes, ATTR_GEN_AI_INPUT_MESSAGES, inputMessages);
536
+ assignJsonAttribute(attributes, "input.value", inputMessages);
537
+ attributes["input.mime_type"] = "application/json";
538
+ }
539
+ }
540
+ if (policy.toolDefinitions) {
541
+ const toolDefinitions = normalizeGenAiToolDefinitions(content?.toolDefinitions);
542
+ if (toolDefinitions.length > 0) assignJsonAttribute(attributes, ATTR_GEN_AI_TOOL_DEFINITIONS, toolDefinitions);
543
+ }
544
+ if (policy.outputMessages) {
545
+ const outputMessages = normalizeGenAiMessages(content?.outputMessages, "assistant");
546
+ if (outputMessages.length > 0) {
547
+ assignJsonAttribute(attributes, ATTR_GEN_AI_OUTPUT_MESSAGES, outputMessages);
548
+ assignJsonAttribute(attributes, "output.value", outputMessages);
549
+ attributes["output.mime_type"] = "application/json";
550
+ }
551
+ }
277
552
  }
278
553
  function assignOtelContentAttribute(attributes, key, value) {
279
554
  const normalized = normalizeOtelContentValue(value);
280
555
  if (normalized) attributes[key] = normalized;
281
556
  }
282
- function assignOtelModelContentAttributes(attributes, event, policy) {
283
- if (policy.inputMessages) assignOtelContentAttribute(attributes, "openclaw.content.input_messages", event.inputMessages);
284
- if (policy.outputMessages) assignOtelContentAttribute(attributes, "openclaw.content.output_messages", event.outputMessages);
285
- if (policy.systemPrompt) assignOtelContentAttribute(attributes, "openclaw.content.system_prompt", event.systemPrompt);
557
+ function assignOtelModelContentAttributes(attributes, content, policy) {
558
+ assignGenAiModelContentAttributes(attributes, content, policy);
559
+ if (policy.inputMessages) assignOtelContentAttribute(attributes, "openclaw.content.input_messages", content?.inputMessages);
560
+ if (policy.toolDefinitions) assignOtelContentAttribute(attributes, "openclaw.content.tool_definitions", content?.toolDefinitions);
561
+ if (policy.outputMessages) assignOtelContentAttribute(attributes, "openclaw.content.output_messages", content?.outputMessages);
562
+ if (policy.systemPrompt) assignOtelContentAttribute(attributes, "openclaw.content.system_prompt", content?.systemPrompt);
286
563
  }
287
564
  function assignOtelToolContentAttributes(attributes, event, policy) {
288
565
  if (policy.toolInputs) assignOtelContentAttribute(attributes, "openclaw.content.tool_input", event.toolInput);
@@ -382,7 +659,7 @@ function createDiagnosticsOtelService() {
382
659
  await stopStarted();
383
660
  const cfg = ctx.config.diagnostics;
384
661
  const otel = cfg?.otel;
385
- if (!cfg?.enabled || !otel?.enabled) return;
662
+ if (!cfg || cfg.enabled === false || !otel?.enabled) return;
386
663
  const emitExporterEvent = (event) => {
387
664
  try {
388
665
  ctx.internalDiagnostics?.emit({
@@ -657,10 +934,18 @@ function createDiagnosticsOtelService() {
657
934
  unit: "ms",
658
935
  description: "Elapsed time before the first streamed model response event"
659
936
  });
937
+ const modelFailoverCounter = meter.createCounter("openclaw.model.failover", {
938
+ unit: "1",
939
+ description: "Model failovers by source, destination, lane, and reason"
940
+ });
660
941
  const toolExecutionDurationHistogram = meter.createHistogram("openclaw.tool.execution.duration_ms", {
661
942
  unit: "ms",
662
943
  description: "Tool execution duration"
663
944
  });
945
+ const toolExecutionBlockedCounter = meter.createCounter("openclaw.tool.execution.blocked", {
946
+ unit: "1",
947
+ description: "Tool executions blocked by policy or sandbox diagnostics"
948
+ });
664
949
  const execProcessDurationHistogram = meter.createHistogram("openclaw.exec.duration_ms", {
665
950
  unit: "ms",
666
951
  description: "Exec process duration"
@@ -693,6 +978,14 @@ function createDiagnosticsOtelService() {
693
978
  unit: "1",
694
979
  description: "Async diagnostic queue drops by dropped event class"
695
980
  });
981
+ const payloadLargeCounter = meter.createCounter("openclaw.payload.large", {
982
+ unit: "1",
983
+ description: "Oversized payload diagnostics by surface and action"
984
+ });
985
+ const payloadLargeBytesHistogram = meter.createHistogram("openclaw.payload.large_bytes", {
986
+ unit: "By",
987
+ description: "Oversized payload byte sizes by surface and action"
988
+ });
696
989
  const livenessWarningCounter = meter.createCounter("openclaw.liveness.warning", {
697
990
  unit: "1",
698
991
  description: "Diagnostic liveness warning events"
@@ -773,6 +1066,7 @@ function createDiagnosticsOtelService() {
773
1066
  const parentContext = "parentContext" in options ? options.parentContext ?? void 0 : void 0;
774
1067
  return tracer.startSpan(name, {
775
1068
  attributes: redactOtelAttributes(attributes),
1069
+ ...options.kind !== void 0 ? { kind: options.kind } : {},
776
1070
  ...startTime !== void 0 ? { startTime } : {}
777
1071
  }, parentContext);
778
1072
  };
@@ -1277,6 +1571,16 @@ function createDiagnosticsOtelService() {
1277
1571
  }).end(evt.ts);
1278
1572
  };
1279
1573
  const recordModelFailover = (evt, metadata) => {
1574
+ const metricAttrs = {
1575
+ "openclaw.failover.reason": lowCardinalityAttr(evt.reason, "unknown"),
1576
+ "openclaw.failover.suspended": evt.suspended === void 0 ? "unknown" : String(evt.suspended),
1577
+ "openclaw.lane": lowCardinalityQueueLaneAttr(evt.lane, "unknown"),
1578
+ "openclaw.model": lowCardinalityAttr(evt.fromModel),
1579
+ "openclaw.provider": lowCardinalityAttr(evt.fromProvider),
1580
+ "openclaw.failover.to_model": lowCardinalityAttr(evt.toModel),
1581
+ "openclaw.failover.to_provider": lowCardinalityAttr(evt.toProvider)
1582
+ };
1583
+ modelFailoverCounter.add(1, metricAttrs);
1280
1584
  if (!tracesEnabled) return;
1281
1585
  const spanAttrs = { "openclaw.failover.reason": lowCardinalityAttr(evt.reason, "unknown") };
1282
1586
  if (evt.fromProvider) spanAttrs["openclaw.provider"] = evt.fromProvider;
@@ -1320,12 +1624,13 @@ function createDiagnosticsOtelService() {
1320
1624
  assignGenAiModelCallAttrs(spanAttrs, evt);
1321
1625
  if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1322
1626
  if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1323
- trackTrustedSpan(evt, metadata, spanWithDuration("openclaw.model.call", spanAttrs, void 0, {
1627
+ trackTrustedSpan(evt, metadata, spanWithDuration(modelCallSpanName(evt), spanAttrs, void 0, {
1628
+ kind: modelCallSpanKind(),
1324
1629
  parentContext: activeTrustedParentContext(evt, metadata),
1325
1630
  startTimeMs: evt.ts
1326
1631
  }));
1327
1632
  };
1328
- const recordModelCallCompleted = (evt, metadata) => {
1633
+ const recordModelCallCompleted = (evt, metadata, modelContent) => {
1329
1634
  const metricAttrs = modelCallMetricAttrs(evt);
1330
1635
  modelCallDurationHistogram.record(evt.durationMs, metricAttrs);
1331
1636
  recordModelCallSizeTimingMetrics(evt, metricAttrs);
@@ -1339,8 +1644,9 @@ function createDiagnosticsOtelService() {
1339
1644
  if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1340
1645
  if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1341
1646
  assignModelCallSizeTimingAttrs(spanAttrs, evt);
1342
- assignOtelModelContentAttributes(spanAttrs, evt, contentCapturePolicy);
1343
- const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
1647
+ assignOtelModelContentAttributes(spanAttrs, modelContent, contentCapturePolicy);
1648
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(modelCallSpanName(evt), spanAttrs, evt.durationMs, {
1649
+ kind: modelCallSpanKind(),
1344
1650
  parentContext: activeTrustedParentContext(evt, metadata),
1345
1651
  endTimeMs: evt.ts
1346
1652
  });
@@ -1348,7 +1654,7 @@ function createDiagnosticsOtelService() {
1348
1654
  addUpstreamRequestIdSpanEvent(span, evt.upstreamRequestIdHash);
1349
1655
  span.end(evt.ts);
1350
1656
  };
1351
- const recordModelCallError = (evt, metadata) => {
1657
+ const recordModelCallError = (evt, metadata, modelContent) => {
1352
1658
  const errorType = lowCardinalityAttr(evt.errorCategory, "other");
1353
1659
  const metricAttrs = {
1354
1660
  ...modelCallMetricAttrs(evt),
@@ -1370,8 +1676,9 @@ function createDiagnosticsOtelService() {
1370
1676
  if (evt.api) spanAttrs["openclaw.api"] = evt.api;
1371
1677
  if (evt.transport) spanAttrs["openclaw.transport"] = evt.transport;
1372
1678
  assignModelCallSizeTimingAttrs(spanAttrs, evt);
1373
- assignOtelModelContentAttributes(spanAttrs, evt, contentCapturePolicy);
1374
- const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration("openclaw.model.call", spanAttrs, evt.durationMs, {
1679
+ assignOtelModelContentAttributes(spanAttrs, modelContent, contentCapturePolicy);
1680
+ const span = takeTrackedTrustedSpan(evt, metadata) ?? spanWithDuration(modelCallSpanName(evt), spanAttrs, evt.durationMs, {
1681
+ kind: modelCallSpanKind(),
1375
1682
  parentContext: activeTrustedParentContext(evt, metadata),
1376
1683
  endTimeMs: evt.ts
1377
1684
  });
@@ -1458,6 +1765,10 @@ function createDiagnosticsOtelService() {
1458
1765
  span.end(evt.ts);
1459
1766
  };
1460
1767
  const recordToolExecutionBlocked = (evt, metadata) => {
1768
+ toolExecutionBlockedCounter.add(1, {
1769
+ ...toolExecutionBaseAttrs(evt),
1770
+ "openclaw.deniedReason": lowCardinalityAttr(evt.deniedReason, "other")
1771
+ });
1461
1772
  if (!tracesEnabled) return;
1462
1773
  const spanAttrs = {
1463
1774
  ...toolExecutionBaseAttrs(evt),
@@ -1472,6 +1783,18 @@ function createDiagnosticsOtelService() {
1472
1783
  setSpanAttrs(span, spanAttrs);
1473
1784
  span.end(evt.ts);
1474
1785
  };
1786
+ const recordPayloadLarge = (evt) => {
1787
+ const attrs = {
1788
+ "openclaw.payload.action": evt.action,
1789
+ "openclaw.payload.surface": lowCardinalityAttr(evt.surface, "unknown"),
1790
+ "openclaw.channel": lowCardinalityAttr(evt.channel, "none"),
1791
+ "openclaw.plugin": lowCardinalityAttr(evt.pluginId, "none"),
1792
+ "openclaw.reason": lowCardinalityAttr(evt.reason, "none")
1793
+ };
1794
+ payloadLargeCounter.add(1, attrs);
1795
+ const bytes = positiveFiniteNumber(evt.bytes);
1796
+ if (bytes !== void 0) payloadLargeBytesHistogram.record(bytes, attrs);
1797
+ };
1475
1798
  const recordExecProcessCompleted = (evt) => {
1476
1799
  const attrs = {
1477
1800
  "openclaw.exec.target": evt.target,
@@ -1555,7 +1878,7 @@ function createDiagnosticsOtelService() {
1555
1878
  ctx.logger.error("diagnostics-otel: internal diagnostics capability unavailable");
1556
1879
  return;
1557
1880
  }
1558
- unsubscribe = subscribe((evt, metadata) => {
1881
+ unsubscribe = subscribe((evt, metadata, privateData) => {
1559
1882
  try {
1560
1883
  switch (evt.type) {
1561
1884
  case "model.usage":
@@ -1655,10 +1978,10 @@ function createDiagnosticsOtelService() {
1655
1978
  recordModelCallStarted(evt, metadata);
1656
1979
  return;
1657
1980
  case "model.call.completed":
1658
- recordModelCallCompleted(evt, metadata);
1981
+ recordModelCallCompleted(evt, metadata, privateData.modelContent);
1659
1982
  return;
1660
1983
  case "model.call.error":
1661
- recordModelCallError(evt, metadata);
1984
+ recordModelCallError(evt, metadata, privateData.modelContent);
1662
1985
  return;
1663
1986
  case "tool.execution.started":
1664
1987
  recordToolExecutionStarted(evt, metadata);
@@ -1696,7 +2019,9 @@ function createDiagnosticsOtelService() {
1696
2019
  case "telemetry.exporter":
1697
2020
  recordTelemetryExporter(evt, metadata);
1698
2021
  return;
1699
- case "payload.large": return;
2022
+ case "payload.large":
2023
+ recordPayloadLarge(evt);
2024
+ return;
1700
2025
  case "model.failover":
1701
2026
  recordModelFailover(evt, metadata);
1702
2027
  return;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/diagnostics-otel",
3
- "version": "2026.5.24-beta.2",
3
+ "version": "2026.5.26-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/diagnostics-otel",
9
- "version": "2026.5.24-beta.2",
9
+ "version": "2026.5.26-beta.1",
10
10
  "dependencies": {
11
11
  "@opentelemetry/api": "1.9.1",
12
12
  "@opentelemetry/api-logs": "0.218.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/diagnostics-otel",
3
- "version": "2026.5.24-beta.2",
3
+ "version": "2026.5.26-beta.1",
4
4
  "description": "OpenClaw diagnostics OpenTelemetry exporter",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,10 +31,10 @@
31
31
  "minHostVersion": ">=2026.4.25"
32
32
  },
33
33
  "compat": {
34
- "pluginApi": ">=2026.5.24-beta.2"
34
+ "pluginApi": ">=2026.5.26-beta.1"
35
35
  },
36
36
  "build": {
37
- "openclawVersion": "2026.5.24-beta.2"
37
+ "openclawVersion": "2026.5.26-beta.1"
38
38
  },
39
39
  "release": {
40
40
  "publishToClawHub": true,
@@ -50,7 +50,7 @@
50
50
  "npm-shrinkwrap.json"
51
51
  ],
52
52
  "peerDependencies": {
53
- "openclaw": ">=2026.5.24-beta.2"
53
+ "openclaw": ">=2026.5.26-beta.1"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "openclaw": {