agent-inspect 6.31.6 → 6.31.8

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +1 -1
  3. package/docs/AI-SDK-ADOPTION.md +62 -34
  4. package/package.json +1 -1
  5. package/packages/cli/dist/{chunk-YXDNWHNJ.mjs → chunk-6FE47XSF.mjs} +119 -34
  6. package/packages/cli/dist/chunk-6FE47XSF.mjs.map +1 -0
  7. package/packages/cli/dist/index.cjs +569 -102
  8. package/packages/cli/dist/index.cjs.map +1 -1
  9. package/packages/cli/dist/index.mjs +454 -74
  10. package/packages/cli/dist/index.mjs.map +1 -1
  11. package/packages/cli/dist/{src-H22KNWAZ.mjs → src-NXAYHG34.mjs} +3 -3
  12. package/packages/cli/dist/{src-H22KNWAZ.mjs.map → src-NXAYHG34.mjs.map} +1 -1
  13. package/packages/core/dist/advanced.cjs +116 -31
  14. package/packages/core/dist/advanced.cjs.map +1 -1
  15. package/packages/core/dist/advanced.mjs +7 -7
  16. package/packages/core/dist/checks.mjs +2 -2
  17. package/packages/core/dist/{chunk-LXYPM2ZO.mjs → chunk-2CCRUI42.mjs} +19 -318
  18. package/packages/core/dist/chunk-2CCRUI42.mjs.map +1 -0
  19. package/packages/core/dist/{chunk-Q5SLKV2Y.mjs → chunk-2IRRUNIN.mjs} +28 -5
  20. package/packages/core/dist/chunk-2IRRUNIN.mjs.map +1 -0
  21. package/packages/core/dist/{chunk-EXGQLMVB.mjs → chunk-7YYS742B.mjs} +49 -12
  22. package/packages/core/dist/chunk-7YYS742B.mjs.map +1 -0
  23. package/packages/core/dist/{chunk-OSGNGRHF.mjs → chunk-IJJSPJXV.mjs} +51 -25
  24. package/packages/core/dist/chunk-IJJSPJXV.mjs.map +1 -0
  25. package/packages/core/dist/chunk-L5WLVBW4.mjs +355 -0
  26. package/packages/core/dist/chunk-L5WLVBW4.mjs.map +1 -0
  27. package/packages/core/dist/exporters.cjs +327 -58
  28. package/packages/core/dist/exporters.cjs.map +1 -1
  29. package/packages/core/dist/exporters.mjs +281 -49
  30. package/packages/core/dist/exporters.mjs.map +1 -1
  31. package/packages/core/dist/persisted.cjs +361 -9
  32. package/packages/core/dist/persisted.cjs.map +1 -1
  33. package/packages/core/dist/persisted.d.cts +8 -1
  34. package/packages/core/dist/persisted.d.ts +8 -1
  35. package/packages/core/dist/persisted.mjs +3 -2
  36. package/packages/core/dist/persisted.mjs.map +1 -1
  37. package/packages/core/dist/readers.cjs +425 -100
  38. package/packages/core/dist/readers.cjs.map +1 -1
  39. package/packages/core/dist/readers.mjs +3 -3
  40. package/packages/cli/dist/chunk-YXDNWHNJ.mjs.map +0 -1
  41. package/packages/core/dist/chunk-EXGQLMVB.mjs.map +0 -1
  42. package/packages/core/dist/chunk-FWK3Q3OJ.mjs +0 -56
  43. package/packages/core/dist/chunk-FWK3Q3OJ.mjs.map +0 -1
  44. package/packages/core/dist/chunk-LXYPM2ZO.mjs.map +0 -1
  45. package/packages/core/dist/chunk-OSGNGRHF.mjs.map +0 -1
  46. package/packages/core/dist/chunk-Q5SLKV2Y.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { buildRunWhatSummary, renderRunWhat, renderTimeline, buildRunTimeline } from './chunk-EXGQLMVB.mjs';
1
+ import { buildRunWhatSummary, renderRunWhat, renderTimeline, buildRunTimeline } from './chunk-7YYS742B.mjs';
2
2
  import { summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml } from './chunk-N5PZAI6Q.mjs';
3
3
  import { escapeHtml, flattenTree, safeString, compactAttributes, stableJson, escapeMarkdown, zeroKinds } from './chunk-CAWPF22J.mjs';
4
4
  export { compactAttributes, escapeHtml, escapeMarkdown, flattenTree, safeString, stableJson, summarizeTree } from './chunk-CAWPF22J.mjs';
@@ -740,11 +740,13 @@ function exportOpenInference(tree, options) {
740
740
  endNs = startNs + unixNano(ev.durationMs);
741
741
  }
742
742
  const { openInferenceKind } = mapInspectKindToOI(ev.kind, warnings);
743
+ const meta = ev.attributes;
744
+ const originalSource = typeof meta?.originalSourceType === "string" && meta.originalSourceType.trim() !== "" ? meta.originalSourceType.trim() : ev.source.type;
743
745
  const attrs = {
744
746
  "openinference.span.kind": openInferenceKind,
745
747
  "agent_inspect.kind": ev.kind,
746
748
  "agent_inspect.confidence": ev.confidence,
747
- "agent_inspect.source.type": ev.source.type,
749
+ "agent_inspect.source.type": originalSource,
748
750
  "agent_inspect.run_id": tree.runId,
749
751
  "agent_inspect.event_id": ev.eventId,
750
752
  "agent_inspect.status": ev.status ?? "unset"
@@ -752,9 +754,9 @@ function exportOpenInference(tree, options) {
752
754
  if (ev.durationMs !== void 0) {
753
755
  attrs["agent_inspect.duration_ms"] = ev.durationMs;
754
756
  }
755
- const meta = ev.attributes;
756
- if (meta?.model !== void 0 && typeof meta.model === "string") {
757
- attrs["llm.model_name"] = meta.model;
757
+ const requestModel = (typeof meta?.model === "string" && meta.model.trim() !== "" ? meta.model : void 0) ?? (typeof meta?.modelId === "string" && meta.modelId.trim() !== "" ? meta.modelId : void 0);
758
+ if (requestModel !== void 0) {
759
+ attrs["llm.model_name"] = requestModel;
758
760
  }
759
761
  const tokens = meta?.tokens;
760
762
  if (tokens && typeof tokens === "object" && tokens !== null) {
@@ -764,8 +766,9 @@ function exportOpenInference(tree, options) {
764
766
  if (typeof outp === "number") attrs["llm.token_count.completion"] = outp;
765
767
  }
766
768
  if (includeAttributes && meta && typeof meta === "object") {
769
+ const skipKeys = /* @__PURE__ */ new Set(["tokens", "model", "modelId", "originalSourceType"]);
767
770
  for (const [k, v] of Object.entries(meta)) {
768
- if (k === "tokens" || k === "model") continue;
771
+ if (skipKeys.has(k)) continue;
769
772
  if (v !== void 0 && v !== null && typeof v !== "object") {
770
773
  attrs[`agent_inspect.preview.${k}`] = typeof v === "string" ? v.slice(0, maxLen) : v;
771
774
  }
@@ -808,6 +811,10 @@ function exportOpenInference(tree, options) {
808
811
  warnings
809
812
  };
810
813
  }
814
+ var OTLP_STATUS_CODE_UNSET = 0;
815
+ var OTLP_STATUS_CODE_OK = 1;
816
+ var OTLP_STATUS_CODE_ERROR = 2;
817
+ var OTLP_SPAN_KIND_INTERNAL = 1;
811
818
  function hexFrom2(seed, byteLen) {
812
819
  return crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
813
820
  }
@@ -820,7 +827,19 @@ function stringAttr(key, value) {
820
827
  return { key, value: { stringValue: value } };
821
828
  }
822
829
  function intAttr(key, value) {
823
- return { key, value: { intValue: String(value) } };
830
+ return { key, value: { intValue: String(Math.trunc(value)) } };
831
+ }
832
+ function doubleAttr(key, value) {
833
+ return { key, value: { doubleValue: value } };
834
+ }
835
+ function boolAttr(key, value) {
836
+ return { key, value: { boolValue: value } };
837
+ }
838
+ function numberAttr(key, value) {
839
+ if (Number.isFinite(value) && !Number.isInteger(value)) {
840
+ return doubleAttr(key, value);
841
+ }
842
+ return intAttr(key, value);
824
843
  }
825
844
  function genAiOperationName(kind) {
826
845
  switch (kind) {
@@ -834,10 +853,27 @@ function genAiOperationName(kind) {
834
853
  return void 0;
835
854
  }
836
855
  }
856
+ function resolveSpanTimes(ev) {
857
+ const startedAtMs = typeof ev.attributes?.startedAtMs === "number" && Number.isFinite(ev.attributes.startedAtMs) ? ev.attributes.startedAtMs : void 0;
858
+ const endedAtMs = typeof ev.attributes?.endedAtMs === "number" && Number.isFinite(ev.attributes.endedAtMs) ? ev.attributes.endedAtMs : void 0;
859
+ const startMs = startedAtMs ?? ev.timestamp;
860
+ const startNs = String(unixNano2(startMs));
861
+ if (endedAtMs !== void 0) {
862
+ return { startNs, endNs: String(unixNano2(endedAtMs)) };
863
+ }
864
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
865
+ return {
866
+ startNs,
867
+ endNs: String(unixNano2(startMs) + unixNano2(ev.durationMs))
868
+ };
869
+ }
870
+ return { startNs };
871
+ }
837
872
  function exportOtlpJson(tree, options) {
838
873
  const warnings = [
839
874
  "OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
840
- "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
875
+ "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload.",
876
+ "Status and span kind use OTLP numeric enums (StatusCode 0/1/2, SpanKind INTERNAL=1)."
841
877
  ];
842
878
  const traceId = hexFrom2(`trace:${tree.runId}`, 16);
843
879
  const includeAttributes = options?.includeAttributes ?? false;
@@ -849,29 +885,34 @@ function exportOtlpJson(tree, options) {
849
885
  const ev = n.event;
850
886
  const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
851
887
  const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
852
- const startNs = String(unixNano2(ev.timestamp));
853
- let endNs;
854
- if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
855
- endNs = String(unixNano2(ev.timestamp) + unixNano2(ev.durationMs));
856
- }
888
+ const { startNs, endNs } = resolveSpanTimes(ev);
889
+ const meta = ev.attributes;
890
+ const originalSource = typeof meta?.originalSourceType === "string" && meta.originalSourceType.trim() !== "" ? meta.originalSourceType.trim() : ev.source.type;
857
891
  const attrs = [
858
892
  stringAttr("agent_inspect.kind", ev.kind),
859
893
  stringAttr("agent_inspect.confidence", ev.confidence),
860
- stringAttr("agent_inspect.source.type", ev.source.type),
894
+ stringAttr("agent_inspect.source.type", originalSource),
861
895
  stringAttr("agent_inspect.run_id", tree.runId),
862
896
  stringAttr("agent_inspect.event_id", ev.eventId),
863
897
  stringAttr("agent_inspect.status", ev.status ?? "unset")
864
898
  ];
865
899
  if (ev.durationMs !== void 0) {
866
- attrs.push(intAttr("agent_inspect.duration_ms", ev.durationMs));
900
+ attrs.push(numberAttr("agent_inspect.duration_ms", ev.durationMs));
867
901
  }
868
902
  const op = genAiOperationName(ev.kind);
869
903
  if (op !== void 0) {
870
904
  attrs.push(stringAttr("gen_ai.operation.name", op));
871
905
  }
872
- const meta = ev.attributes;
873
- if (meta?.model !== void 0 && typeof meta.model === "string") {
874
- attrs.push(stringAttr("gen_ai.request.model", meta.model.slice(0, maxLen)));
906
+ const requestModel = (typeof meta?.model === "string" && meta.model.trim() !== "" ? meta.model : void 0) ?? (typeof meta?.modelId === "string" && meta.modelId.trim() !== "" ? meta.modelId : void 0);
907
+ if (requestModel !== void 0) {
908
+ attrs.push(stringAttr("gen_ai.request.model", requestModel.slice(0, maxLen)));
909
+ }
910
+ const responseModel = typeof meta?.responseModelId === "string" && meta.responseModelId.trim() !== "" ? meta.responseModelId : typeof meta?.responseModel === "string" && meta.responseModel.trim() !== "" ? meta.responseModel : void 0;
911
+ if (responseModel !== void 0) {
912
+ attrs.push(stringAttr("gen_ai.response.model", responseModel.slice(0, maxLen)));
913
+ }
914
+ if (typeof meta?.provider === "string" && meta.provider.trim() !== "") {
915
+ attrs.push(stringAttr("gen_ai.provider.name", meta.provider.slice(0, maxLen)));
875
916
  }
876
917
  const tokens = meta?.tokens;
877
918
  if (tokens && typeof tokens === "object" && tokens !== null) {
@@ -881,31 +922,39 @@ function exportOtlpJson(tree, options) {
881
922
  if (typeof outp === "number") attrs.push(intAttr("gen_ai.usage.output_tokens", outp));
882
923
  }
883
924
  if (includeAttributes && meta && typeof meta === "object") {
925
+ const skipKeys = /* @__PURE__ */ new Set([
926
+ "tokens",
927
+ "model",
928
+ "modelId",
929
+ "responseModel",
930
+ "responseModelId",
931
+ "provider",
932
+ "originalSourceType"
933
+ ]);
884
934
  for (const [k, v] of Object.entries(meta)) {
885
- if (k === "tokens" || k === "model") continue;
886
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
887
- attrs.push(
888
- stringAttr(
889
- `agent_inspect.preview.${k}`,
890
- typeof v === "string" ? v.slice(0, maxLen) : String(v)
891
- )
892
- );
935
+ if (skipKeys.has(k)) continue;
936
+ if (typeof v === "string") {
937
+ attrs.push(stringAttr(`agent_inspect.preview.${k}`, v.slice(0, maxLen)));
938
+ } else if (typeof v === "number" && Number.isFinite(v)) {
939
+ attrs.push(numberAttr(`agent_inspect.preview.${k}`, v));
940
+ } else if (typeof v === "boolean") {
941
+ attrs.push(boolAttr(`agent_inspect.preview.${k}`, v));
893
942
  }
894
943
  }
895
944
  }
896
- let statusCode = "STATUS_CODE_UNSET";
945
+ let statusCode = OTLP_STATUS_CODE_UNSET;
897
946
  let statusMessage;
898
947
  if (ev.status === "error") {
899
- statusCode = "STATUS_CODE_ERROR";
948
+ statusCode = OTLP_STATUS_CODE_ERROR;
900
949
  statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
901
950
  } else if (ev.status === "ok") {
902
- statusCode = "STATUS_CODE_OK";
951
+ statusCode = OTLP_STATUS_CODE_OK;
903
952
  }
904
953
  const spanJson = {
905
954
  traceId,
906
955
  spanId,
907
956
  name: ev.name,
908
- kind: "SPAN_KIND_INTERNAL",
957
+ kind: OTLP_SPAN_KIND_INTERNAL,
909
958
  startTimeUnixNano: startNs,
910
959
  attributes: attrs,
911
960
  status: {
@@ -947,6 +996,207 @@ function exportOtlpJson(tree, options) {
947
996
 
948
997
  // packages/core/src/exporters/validation.ts
949
998
  var EXPERIMENTAL = "Experimental compatibility export \u2014 verify against your target tooling before relying on it.";
999
+ function pushPath(errors, path, message) {
1000
+ errors.push(`${path}: ${message}`);
1001
+ }
1002
+ function isRecord2(value) {
1003
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1004
+ }
1005
+ function isHexId(value, byteLen) {
1006
+ return typeof value === "string" && new RegExp(`^[0-9a-fA-F]{${byteLen * 2}}$`).test(value);
1007
+ }
1008
+ function validateAnyValue(errors, path, value) {
1009
+ if (!isRecord2(value)) {
1010
+ pushPath(errors, path, "must be an AnyValue object");
1011
+ return;
1012
+ }
1013
+ const variants = [
1014
+ "stringValue",
1015
+ "boolValue",
1016
+ "intValue",
1017
+ "doubleValue",
1018
+ "bytesValue",
1019
+ "arrayValue",
1020
+ "kvlistValue"
1021
+ ].filter((key) => value[key] !== void 0);
1022
+ if (variants.length !== 1) {
1023
+ pushPath(
1024
+ errors,
1025
+ path,
1026
+ `AnyValue must set exactly one variant (found ${variants.length}: ${variants.join(",")})`
1027
+ );
1028
+ }
1029
+ if (value.intValue !== void 0) {
1030
+ if (typeof value.intValue !== "string" || !/^-?\d+$/.test(value.intValue)) {
1031
+ pushPath(errors, `${path}.intValue`, "must be a decimal integer string");
1032
+ }
1033
+ }
1034
+ }
1035
+ function validateOtlpJson(content) {
1036
+ const errors = [];
1037
+ const warnings = [
1038
+ EXPERIMENTAL,
1039
+ "OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation.",
1040
+ "producer-profile checks IDs, timing, and AnyValue shape; historical string enums are reader-compat warnings only."
1041
+ ];
1042
+ let parsed;
1043
+ try {
1044
+ parsed = JSON.parse(content);
1045
+ } catch {
1046
+ pushPath(errors, "$", "OTLP JSON export is not valid JSON");
1047
+ return { ok: false, format: "otlp-json", errors, warnings };
1048
+ }
1049
+ if (!isRecord2(parsed)) {
1050
+ pushPath(errors, "$", "OTLP JSON export must be an object");
1051
+ return { ok: false, format: "otlp-json", errors, warnings };
1052
+ }
1053
+ if (!Array.isArray(parsed.resourceSpans)) {
1054
+ pushPath(errors, "$.resourceSpans", "must be an array");
1055
+ return { ok: false, format: "otlp-json", errors, warnings };
1056
+ }
1057
+ parsed.resourceSpans.forEach((resourceSpan, rsi) => {
1058
+ const rsPath = `$.resourceSpans[${rsi}]`;
1059
+ if (!isRecord2(resourceSpan)) {
1060
+ pushPath(errors, rsPath, "must be an object");
1061
+ return;
1062
+ }
1063
+ if (!Array.isArray(resourceSpan.scopeSpans)) {
1064
+ pushPath(errors, `${rsPath}.scopeSpans`, "must be an array");
1065
+ return;
1066
+ }
1067
+ resourceSpan.scopeSpans.forEach((scopeSpan, ssi) => {
1068
+ const ssPath = `${rsPath}.scopeSpans[${ssi}]`;
1069
+ if (!isRecord2(scopeSpan)) {
1070
+ pushPath(errors, ssPath, "must be an object");
1071
+ return;
1072
+ }
1073
+ if (!Array.isArray(scopeSpan.spans)) {
1074
+ pushPath(errors, `${ssPath}.spans`, "must be an array");
1075
+ return;
1076
+ }
1077
+ scopeSpan.spans.forEach((span, spi) => {
1078
+ const spanPath = `${ssPath}.spans[${spi}]`;
1079
+ if (!isRecord2(span)) {
1080
+ pushPath(errors, spanPath, "must be an object");
1081
+ return;
1082
+ }
1083
+ if (!isHexId(span.traceId, 16)) {
1084
+ pushPath(
1085
+ errors,
1086
+ `${spanPath}.traceId`,
1087
+ "must be a 32-char hex trace id (16 bytes)"
1088
+ );
1089
+ }
1090
+ if (!isHexId(span.spanId, 8)) {
1091
+ pushPath(
1092
+ errors,
1093
+ `${spanPath}.spanId`,
1094
+ "must be a 16-char hex span id (8 bytes)"
1095
+ );
1096
+ }
1097
+ if (span.parentSpanId !== void 0 && !isHexId(span.parentSpanId, 8)) {
1098
+ pushPath(
1099
+ errors,
1100
+ `${spanPath}.parentSpanId`,
1101
+ "must be a 16-char hex span id when present"
1102
+ );
1103
+ }
1104
+ if (typeof span.name !== "string") {
1105
+ pushPath(errors, `${spanPath}.name`, "must be a string");
1106
+ }
1107
+ if (typeof span.startTimeUnixNano !== "string" || !/^\d+$/.test(span.startTimeUnixNano)) {
1108
+ pushPath(
1109
+ errors,
1110
+ `${spanPath}.startTimeUnixNano`,
1111
+ "must be a decimal-string 64-bit integer"
1112
+ );
1113
+ }
1114
+ if (span.endTimeUnixNano !== void 0 && (typeof span.endTimeUnixNano !== "string" || !/^\d+$/.test(span.endTimeUnixNano))) {
1115
+ pushPath(
1116
+ errors,
1117
+ `${spanPath}.endTimeUnixNano`,
1118
+ "must be a decimal-string 64-bit integer when present"
1119
+ );
1120
+ }
1121
+ if (typeof span.startTimeUnixNano === "string" && /^\d+$/.test(span.startTimeUnixNano) && typeof span.endTimeUnixNano === "string" && /^\d+$/.test(span.endTimeUnixNano)) {
1122
+ try {
1123
+ if (BigInt(span.endTimeUnixNano) < BigInt(span.startTimeUnixNano)) {
1124
+ pushPath(
1125
+ errors,
1126
+ `${spanPath}.endTimeUnixNano`,
1127
+ "must be >= startTimeUnixNano"
1128
+ );
1129
+ }
1130
+ } catch {
1131
+ pushPath(errors, `${spanPath}.endTimeUnixNano`, "out of integer range");
1132
+ }
1133
+ }
1134
+ if (Array.isArray(span.attributes)) {
1135
+ span.attributes.forEach((attr, ai) => {
1136
+ const attrPath = `${spanPath}.attributes[${ai}]`;
1137
+ if (!isRecord2(attr)) {
1138
+ pushPath(errors, attrPath, "must be an object");
1139
+ return;
1140
+ }
1141
+ if (typeof attr.key !== "string") {
1142
+ pushPath(errors, `${attrPath}.key`, "must be a string");
1143
+ }
1144
+ validateAnyValue(errors, `${attrPath}.value`, attr.value);
1145
+ });
1146
+ }
1147
+ if (typeof span.kind === "number") {
1148
+ if (!Number.isInteger(span.kind)) {
1149
+ pushPath(errors, `${spanPath}.kind`, "must be an integer SpanKind enum");
1150
+ }
1151
+ } else if (typeof span.kind === "string") {
1152
+ warnings.push(
1153
+ `${spanPath}.kind: string SpanKind is historical reader-compat only; producer wire format uses numeric enums`
1154
+ );
1155
+ } else if (span.kind !== void 0) {
1156
+ pushPath(
1157
+ errors,
1158
+ `${spanPath}.kind`,
1159
+ "must be numeric SpanKind or historical string enum"
1160
+ );
1161
+ }
1162
+ if (!isRecord2(span.status)) {
1163
+ pushPath(errors, `${spanPath}.status`, "must be an object");
1164
+ } else {
1165
+ const code = span.status.code;
1166
+ if (typeof code === "number") {
1167
+ if (![0, 1, 2].includes(code)) {
1168
+ pushPath(
1169
+ errors,
1170
+ `${spanPath}.status.code`,
1171
+ "must be numeric StatusCode 0 (UNSET), 1 (OK), or 2 (ERROR)"
1172
+ );
1173
+ }
1174
+ } else if (typeof code === "string") {
1175
+ const upper = code.toUpperCase();
1176
+ if (upper !== "STATUS_CODE_UNSET" && upper !== "STATUS_CODE_OK" && upper !== "STATUS_CODE_ERROR" && upper !== "UNSET" && upper !== "OK" && upper !== "ERROR" && !/^[012]$/.test(code.trim())) {
1177
+ pushPath(
1178
+ errors,
1179
+ `${spanPath}.status.code`,
1180
+ "unrecognized StatusCode string"
1181
+ );
1182
+ } else {
1183
+ warnings.push(
1184
+ `${spanPath}.status.code: string StatusCode is historical reader-compat only; producer wire format uses numeric 0/1/2`
1185
+ );
1186
+ }
1187
+ } else {
1188
+ pushPath(
1189
+ errors,
1190
+ `${spanPath}.status.code`,
1191
+ "must be numeric StatusCode 0/1/2 or historical string enum"
1192
+ );
1193
+ }
1194
+ }
1195
+ });
1196
+ });
1197
+ });
1198
+ return { ok: errors.length === 0, format: "otlp-json", errors, warnings };
1199
+ }
950
1200
  function validateExportContent(format, content) {
951
1201
  const errors = [];
952
1202
  const warnings = [EXPERIMENTAL];
@@ -992,25 +1242,7 @@ function validateExportContent(format, content) {
992
1242
  return { ok: errors.length === 0, format, errors, warnings };
993
1243
  }
994
1244
  if (format === "otlp-json") {
995
- let parsed;
996
- try {
997
- parsed = JSON.parse(content);
998
- } catch {
999
- errors.push("OTLP JSON export is not valid JSON");
1000
- return { ok: false, format, errors, warnings };
1001
- }
1002
- if (!parsed || typeof parsed !== "object") {
1003
- errors.push("OTLP JSON export must be an object");
1004
- return { ok: false, format, errors, warnings };
1005
- }
1006
- const o = parsed;
1007
- if (!Array.isArray(o.resourceSpans)) {
1008
- errors.push("OTLP JSON export must include resourceSpans array");
1009
- }
1010
- warnings.push(
1011
- "OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation."
1012
- );
1013
- return { ok: errors.length === 0, format, errors, warnings };
1245
+ return validateOtlpJson(content);
1014
1246
  }
1015
1247
  errors.push(`Unsupported export format`);
1016
1248
  return { ok: false, format, errors, warnings };